모노레포 의존성 관리: ESLint Boundaries 활용
이번 글에서는 ESLint Boundaries 플러그인을 활용한 모노레포 의존성 관리 전략을 다룹니다. 단순히 "조심해서 코딩하자"는 약속을 넘어, 코드 레벨에서 의존성 규칙을 강제하고 아키텍처를 사수하는 구체적인 방법을 알아보겠습니다.
왜 의존성 관리가 중요한가
모노레포가 가지는 잠재적 위험
모노레포 환경에서는 패키지 간의 소스 코드 접근이 매우 자유롭습니다. 이는 장점이지만 동시에 치명적인 단점이 될 수 있습니다. 무분별한 import는 다음과 같은 문제를 야기합니다.
// utils/src/format.ts
import { Button } from '@design-system/components'; // 🚨 Layer 0이 Layer 2를 참조하는 상황
// 발생하는 문제점:
// 1. 순환 참조(Circular Dependency) 발생
// 2. 빌드 순서 꼬임으로 인한 배포 실패
// 3. 버전 관리 및 의존성 그래프의 복잡도 증가
// 4. 불필요한 코드가 포함되어 번들 사이즈 증가
해결 전략: ESLint Boundaries
사람의 기억력이나 리뷰어의 꼼꼼함에 의존하는 것은 한계가 있습니다. ESLint Boundaries를 도입하여 아키텍처 규칙을 린트 에러로 강제하는 것이 가장 확실한 방법입니다.
# 잘못된 import 시도 시 즉시 에러 발생
error '@design-system/components' is not allowed from 'utils' boundaries/element-types
ESLint Boundaries 설정 가이드
전체 설정 파일 예시
// eslint.config.mjs
import boundaries from 'eslint-plugin-boundaries';
import tseslint from '@typescript-eslint/eslint-plugin';
import tsparser from '@typescript-eslint/parser';
export default [
{
files: ['packages/*/src/**/*.{ts,tsx}'],
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: { jsx: true },
},
},
plugins: {
'@typescript-eslint': tseslint,
boundaries,
},
settings: {
// 1. 패키지(Element) 타입 정의
'boundaries/elements': [
{ type: 'components', pattern: ['packages/components/src/**/*'] },
{ type: 'hooks', pattern: ['packages/hooks/src/**/*'] },
{ type: 'theme', pattern: ['packages/theme/src/**/*'] },
{ type: 'utils', pattern: ['packages/utils/src/**/*'] },
{ type: 'core', pattern: ['packages/core/src/**/*'] },
],
// 2. 테스트 파일은 의존성 검사에서 제외
'boundaries/ignore': ['**/*.test.ts', '**/*.test.tsx'],
},
rules: {
// 3. 의존성 규칙 정의
'boundaries/element-types': [
'error',
{
default: 'disallow', // 기본적으로 모든 참조 차단 (Whitelist 방식)
rules: [
// Layer 2: components는 hooks, theme, utils 참조 가능
{ from: 'components', allow: ['hooks', 'theme', 'utils'] },
// Layer 1: hooks는 theme, utils 참조 가능
{ from: 'hooks', allow: ['theme', 'utils'] },
// Layer 0: theme, utils는 외부 의존성 없음
{ from: 'theme', allow: [] },
{ from: 'utils', allow: [] },
// Main: core는 모든 패키지 참조 가능
{ from: 'core', allow: ['components', 'hooks', 'theme', 'utils'] },
],
},
],
},
},
];
설정 상세 포인트
1. 패키지 정의 (boundaries/elements)
type은 규칙에서 사용할 식별자이고 pattern은 실제 파일 경로입니다. 이 매핑을 통해 린터가 현재 파일이 어떤 패키지에 속하는지 인식합니다.
2. 예외 처리 (boundaries/ignore)
테스트 코드는 통합 테스트 등의 이유로 상위 레이어에서 하위 레이어를 참조하거나 그 반대의 경우도 발생할 수 있습니다. 따라서 ignore 패턴을 사용하여 검사 대상에서 제외했습니다.
3. 규칙 정의 (boundaries/element-types)
default: 'disallow' 설정을 통해 화이트리스트 방식을 적용했습니다. 즉 "허용된 것 외에는 모두 금지"함으로써 실수를 원천 차단했습니다.
의존성 규칙 매트릭스
설정한 규칙을 표로 정리하면 다음과 같습니다.
| From \ To | utils | theme | hooks | components | core |
|---|---|---|---|---|---|
| utils | - | ✗ | ✗ | ✗ | ✗ |
| theme | ✗ | - | ✗ | ✗ | ✗ |
| hooks | ✓ | ✓ | - | ✗ | ✗ |
| components | ✓ | ✓ | ✓ | - | ✗ |
| core | ✓ | ✓ | ✓ | ✓ | - |
사용 방법 및 검증
린트 실행
# 전체 린트 (의존성 포함)
pnpm lint
# 의존성 규칙만 별도로 빠르게 검사하고 싶을 때
pnpm lint:layers
CI 파이프라인 통합
로컬에서만 검사하면 누군가 --no-verify로 푸시할 수도 있습니다. 따라서 GitLab CI 파이프라인에 검증 단계를 포함시켜, 규칙을 위반한 코드는 머지되지 않도록 강제해야 합니다.
# .gitlab-ci.yml
lint:
stage: test
script:
- pnpm lint
rules:
- if: $CI_COMMIT_BRANCH =~ /^(feature|refactor)\/.+$/
도입 효과
1. 명확한 빌드 파이프라인
레이어 규칙이 지켜지면 빌드 순서가 Layer 0 → Layer 1 → Layer 2 → Main으로 명확해집니다. 이는 Turborepo의 병렬 빌드 효율을 극대화합니다.
2. 예측 가능한 버전 관리
의존성이 단방향으로만 흐르기 때문에 패키지 수정 시 영향 범위를 파악하기 쉽습니다. utils가 바뀌면 이를 의존하는 상위 레이어들만 업데이트하면 되며 역방향 전파는 걱정할 필요가 없습니다.
3. 독립적인 테스트 환경
하위 레이어는 상위 레이어를 알지 못하므로 격리된 상태에서 단위 테스트를 수행하기 수월합니다. utils 패키지만 단독으로 테스트할 때 다른 패키지의 영향을 전혀 받지 않습니다.
'je개발 회고' 카테고리의 다른 글
| [ 디자인 시스템 ] (3) 라이브러리 버전 관리 (0) | 2026.01.12 |
|---|---|
| [ 디자인 시스템 ] (1) 모노레포 아키텍처 (0) | 2026.01.12 |
| [ 디자인 시스템 ] (0) 모노레포로 사내 라이브러리 만들기 (0) | 2026.01.12 |
| [ CI/CD ] 모노레포 Gitlab CI/CD 전략 (0) | 2025.12.22 |
| [ 번들러 ] 모노레포 번들러 비교 (0) | 2025.12.22 |