프로젝트 레퍼런스
대규모 타입스크립트 프로젝트를 개발하다 보면, 하나의 거대한 tsconfig.json
파일과 단일 컴파일 프로세스로는 효율적인 관리가 어려워지는 시점이 옵니다. 특히 모노레포(Monorepo) 환경에서 여러 개의 하위 프로젝트(패키지)가 서로 의존성을 가질 때 이러한 문제가 두드러집니다. 모든 코드를 한 번에 컴파일하는 것은 시간이 오래 걸리고, 특정 하위 프로젝트의 변경 사항이 전체 빌드를 재시작하게 만들어 개발 생산성을 저해할 수 있습니다.
타입스크립트 3.0부터 도입된 프로젝트 레퍼런스(Project References) 기능은 이러한 문제를 해결하기 위한 강력한 솔루션입니다. 이 기능은 큰 타입스크립트 프로젝트를 여러 개의 작은 tsconfig.json
파일을 가진 하위 프로젝트로 분할하고, 이들이 서로의 의존성을 명시적으로 참조할 수 있도록 합니다.
왜 프로젝트 레퍼런스를 사용해야 하는가?
프로젝트 레퍼런스를 사용하면 다음과 같은 주요 이점을 얻을 수 있습니다.
증분 빌드 (Incremental Builds)
- 가장 큰 장점입니다. 변경된 하위 프로젝트와 그에 의존하는 프로젝트만 다시 컴파일하여 전체 빌드 시간을 크게 단축합니다. 이는 대규모 프로젝트에서 개발자 경험을 혁신적으로 개선합니다.
모듈 경계의 명확화
- 각 하위 프로젝트가 명확한 경계를 가지므로, 코드 베이스의 모듈화 수준을 높이고 의존성을 명확하게 파악할 수 있습니다. 이는 아키텍처 설계와 유지보수에 도움이 됩니다.
코드 이동 및 재구성 용이
- 논리적으로 분리된 하위 프로젝트 덕분에 코드 베이스를 리팩토링하거나 새로운 기능을 추가할 때 훨씬 유연하게 대처할 수 있습니다.
IDE 성능 향상
- IDE는 전체 프로젝트가 아닌 현재 작업 중인 하위 프로젝트와 그 의존성만 분석하여, 자동 완성 및 타입 검사 성능을 향상시킬 수 있습니다.
프로젝트 레퍼런스 설정 방법
프로젝트 레퍼런스를 사용하려면, 의존성을 가지는 각 하위 프로젝트의 tsconfig.json
파일에 몇 가지 변경 사항을 적용해야 합니다.
기본 구조 예시 (모노레포)
1단계: common
프로젝트 (생산자) 설정
common
프로젝트는 다른 프로젝트가 의존할 타입 정의 파일(.d.ts
) 을 생성해야 합니다.
{
"compilerOptions": {
"target": "es2020",
"module": "esnext",
"outDir": "./dist", // 컴파일된 JS 파일과 .d.ts 파일이 저장될 곳
"rootDir": "./src",
"declaration": true, // 필수: .d.ts 파일 생성 활성화
"declarationMap": true, // 선택: .d.ts.map 파일 생성 (원본 TS 파일로 이동 가능)
"composite": true, // 필수: 이 프로젝트가 다른 프로젝트에 의해 참조될 것임을 나타냄
"strict": true
},
"include": [
"src/**/*.ts"
]
}
"declaration": true
: 이 프로젝트가 다른 프로젝트에서 사용할 타입 정보를 포함하는.d.ts
파일을 생성하도록 지시합니다."composite": true
: 이 프로젝트가 다른tsconfig.json
에 의해 참조될 수 있는 프로젝트임을 나타내는 플래그입니다. 이 옵션을 켜면declaration
과declarationMap
이 자동으로true
로 설정됩니다 (명시적으로 써주는 것이 좋습니다).
2단계: ui
프로젝트 (소비자) 설정
ui
프로젝트는 common
프로젝트가 제공하는 타입에 의존합니다. references
배열을 사용하여 이 의존성을 명시합니다.
{
"compilerOptions": {
"target": "es2020",
"module": "esnext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true
},
"include": [
"src/**/*.ts"
],
"references": [
{ "path": "../common" } // common 프로젝트를 참조합니다.
]
}
"references": []
: 이 배열 안에 현재 프로젝트가 의존하는 다른tsconfig.json
파일의 경로를 지정합니다. 경로는 현재tsconfig.json
파일을 기준으로 합니다.
3단계: 루트 tsconfig.json
(빌드 관리)
모노레포의 루트에 있는 tsconfig.json
은 일반적으로 직접 코드를 컴파일하기보다는, 하위 프로젝트들의 빌드 순서를 관리하고 증분 빌드를 트리거하는 역할을 합니다.
{
"files": [], // 이 tsconfig 자체는 컴파일할 파일이 없으므로 비워둡니다.
"references": [
{ "path": "./packages/common" }, // common 프로젝트를 먼저 빌드해야 합니다.
{ "path": "./packages/ui" } // ui 프로젝트는 common에 의존하므로 그 다음에 빌드됩니다.
]
}
"files": []
: 이 루트tsconfig.json
은 직접 소스 파일을 컴파일하지 않고, 단지 하위 프로젝트들을 참조만 하므로files
나include
를 비워두는 것이 일반적입니다."references": []
: 여기에 모든 하위 프로젝트를 나열합니다.tsc --build
명령을 사용하면 TypeScript는 이references
배열을 기반으로 올바른 빌드 순서를 자동으로 추론합니다.
프로젝트 레퍼런스 사용하기
위와 같이 설정한 후, 터미널에서 tsc
명령어를 사용합니다.
# 루트 디렉토리에서 실행
tsc --build # 또는 tsc -b
tsc --build
명령은 다음과 같은 작업을 수행합니다.
my-monorepo/tsconfig.json
파일을 찾습니다.
references
배열에 나열된 모든 프로젝트를 탐색합니다.
프로젝트 간의 의존성 그래프를 생성하여 올바른 빌드 순서를 결정합니다. (예: common
-> ui
)
각 프로젝트를 증분 방식으로 컴파일합니다. 만약 common
프로젝트의 소스가 변경되지 않았다면, ui
프로젝트만 다시 빌드되거나 아예 빌드되지 않을 수 있습니다.
Node.js 패키지 경로 매핑 (선택 사항)
ui
프로젝트에서 common
프로젝트의 모듈을 import { MyType } from 'common/types';
와 같이 Node.js 패키지처럼 가져오려면, ui
프로젝트의 tsconfig.json
에 paths
매핑을 추가해야 합니다.
my-monorepo/packages/ui/tsconfig.json
수정
{
"compilerOptions": {
"target": "es2020",
"module": "esnext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"baseUrl": ".", // paths를 사용하려면 baseUrl이 필요
"paths": {
"common/*": ["../common/src/*"] // common 모듈을 common/src에서 찾도록 매핑
}
},
"include": [
"src/**/*.ts"
],
"references": [
{ "path": "../common" }
]
}
이제 ui
프로젝트의 소스 코드에서 import { MyType } from 'common/types';
와 같이 임포트할 수 있습니다. (./src/types/index.ts
에 있다면 common/types
로 임포트)
모노레포에서의 타입 가져오기
ui
프로젝트에서 common
프로젝트의 타입을 가져오는 방법은 두 가지입니다.
소스 파일 직접 참조:
import { CommonType } from '../../common/src/types';
이 방식은 common
프로젝트가 컴파일되지 않아도 작동하지만, 실제 런타임 의존성과 일치하지 않을 수 있습니다.
컴파일된 .d.ts
파일 참조 (권장):
import { CommonType } from 'common/types';
(위의 paths
설정 필요)
이 방식은 common
프로젝트가 먼저 빌드되어 .d.ts
파일이 생성되어야 합니다. 이것이 프로젝트 레퍼런스의 주된 목적이며, 런타임 의존성과 일치하므로 더 견고한 접근 방식입니다.
주의사항 및 고려사항
composite: true
의 영향:composite
프로젝트는.d.ts
파일 생성이 필수적이며, 일부 컴파일러 옵션(예:noImplicitAny
)이 더 엄격하게 적용될 수 있습니다.- 순환 의존성: 프로젝트 레퍼런스는 순환 의존성을 허용하지 않습니다. 만약 A가 B에 의존하고 B가 A에 의존하는 순환 의존성이 발생하면 빌드 오류가 발생합니다. 이는 아키텍처 문제를 진단하는 데 도움이 됩니다.
- 번들러와의 통합:
tsc --build
는 각 프로젝트를 개별적으로 컴파일하고.js
파일을 생성하지만, 최종적으로 이들을 하나로 묶는 것은 여전히 웹팩, Vite, Rollup과 같은 번들러의 역할입니다. 번들러 설정에서도paths
매핑 등을 동일하게 구성해야 합니다. solution
tsconfig.json
: 모든 하위 프로젝트를 포함하고 빌드를 조정하는 최상위tsconfig.json
을 "Solutiontsconfig.json
"이라고 부르기도 합니다.
프로젝트 레퍼런스는 대규모 타입스크립트 프로젝트, 특히 모노레포 환경에서 개발 효율성과 빌드 성능을 획기적으로 향상시킬 수 있는 강력한 기능입니다. 각 하위 프로젝트의 독립성을 유지하면서도, 타입 안전한 방식으로 서로 의존할 수 있도록 하여 복잡한 코드 베이스를 더 효과적으로 관리할 수 있게 합니다.