프로젝트 레퍼런스
대규모 타입스크립트 프로젝트를 운영하다 보면
하나의 거대한 tsconfig.json과 단일 컴파일 흐름만으로는
효율적인 관리가 어려워지는 시점이 옵니다.
특히 모노레포(Monorepo)에서 여러 하위 프로젝트가 서로 의존할 때 이 문제가 더 뚜렷하게 드러납니다. 모든 코드를 한 번에 컴파일하면 시간이 오래 걸리고, 작은 변경도 전체 빌드를 다시 시작하게 만들어 생산성을 떨어뜨릴 수 있습니다.
타입스크립트 3.0부터 도입된 프로젝트 레퍼런스(Project References) 기능은 이러한 문제를 해결하기 위한 강력한 솔루션입니다. 이 기능은 큰 타입스크립트 프로젝트를 여러 개의 작은 tsconfig.json 파일을 가진 하위 프로젝트로 분할하고, 이들이 서로의 의존성을 명시적으로 참조할 수 있도록 합니다.
왜 프로젝트 레퍼런스를 사용해야 하는가?
프로젝트 레퍼런스를 사용하면 다음과 같은 주요 이점을 얻을 수 있습니다.
- 가장 큰 장점입니다. 변경된 하위 프로젝트와 그에 의존하는 프로젝트만 다시 컴파일하여 전체 빌드 시간을 크게 단축합니다. 이는 대규모 프로젝트에서 개발자 경험을 혁신적으로 개선합니다.
- 각 하위 프로젝트가 명확한 경계를 가지므로, 코드 베이스의 모듈화 수준을 높이고 의존성을 명확하게 파악할 수 있습니다. 이는 아키텍처 설계와 유지보수에 도움이 됩니다.
- 논리적으로 분리된 하위 프로젝트 덕분에 코드 베이스를 리팩토링하거나 새로운 기능을 추가할 때 훨씬 유연하게 대처할 수 있습니다.
- IDE는 전체 프로젝트가 아닌 현재 작업 중인 하위 프로젝트와 그 의존성만 분석하여, 자동 완성 및 타입 검사 성능을 향상시킬 수 있습니다.
프로젝트 레퍼런스 설정 방법
프로젝트 레퍼런스를 사용하려면, 의존성을 가지는 각 하위 프로젝트의 tsconfig.json 파일에 몇 가지 변경 사항을 적용해야 합니다.
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로 설정됩니다 (명시적으로 써주는 것이 좋습니다).
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파일을 기준으로 합니다.
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 -btsc --build 명령은 다음과 같은 작업을 수행합니다.
my-monorepo/tsconfig.json 파일을 찾습니다.
references 배열에 나열된 모든 프로젝트를 탐색합니다.
프로젝트 간의 의존성 그래프를 생성하여 올바른 빌드 순서를 결정합니다. (예: common -> ui)
각 프로젝트를 증분 방식으로 컴파일합니다. 만약 common 프로젝트의 소스가 변경되지 않았다면, ui 프로젝트만 다시 빌드되거나 아예 빌드되지 않을 수 있습니다.
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매핑 등을 동일하게 구성해야 합니다. solutiontsconfig.json: 모든 하위 프로젝트를 포함하고 빌드를 조정하는 최상위tsconfig.json을 "Solutiontsconfig.json"이라고 부르기도 합니다.
프로젝트 레퍼런스는 대규모 타입스크립트 프로젝트, 특히 모노레포 환경에서 개발 효율성과 빌드 성능을 획기적으로 향상시킬 수 있는 강력한 기능입니다. 각 하위 프로젝트의 독립성을 유지하면서도, 타입 안전한 방식으로 서로 의존할 수 있도록 하여 복잡한 코드 베이스를 더 효과적으로 관리할 수 있게 합니다.