icon
9장 : 타입스크립트 컴파일러

프로젝트 레퍼런스


대규모 타입스크립트 프로젝트를 개발하다 보면, 하나의 거대한 tsconfig.json 파일과 단일 컴파일 프로세스로는 효율적인 관리가 어려워지는 시점이 옵니다. 특히 모노레포(Monorepo) 환경에서 여러 개의 하위 프로젝트(패키지)가 서로 의존성을 가질 때 이러한 문제가 두드러집니다. 모든 코드를 한 번에 컴파일하는 것은 시간이 오래 걸리고, 특정 하위 프로젝트의 변경 사항이 전체 빌드를 재시작하게 만들어 개발 생산성을 저해할 수 있습니다.

타입스크립트 3.0부터 도입된 프로젝트 레퍼런스(Project References) 기능은 이러한 문제를 해결하기 위한 강력한 솔루션입니다. 이 기능은 큰 타입스크립트 프로젝트를 여러 개의 작은 tsconfig.json 파일을 가진 하위 프로젝트로 분할하고, 이들이 서로의 의존성을 명시적으로 참조할 수 있도록 합니다.


왜 프로젝트 레퍼런스를 사용해야 하는가?

프로젝트 레퍼런스를 사용하면 다음과 같은 주요 이점을 얻을 수 있습니다.

증분 빌드 (Incremental Builds)

  • 가장 큰 장점입니다. 변경된 하위 프로젝트와 그에 의존하는 프로젝트만 다시 컴파일하여 전체 빌드 시간을 크게 단축합니다. 이는 대규모 프로젝트에서 개발자 경험을 혁신적으로 개선합니다.

모듈 경계의 명확화

  • 각 하위 프로젝트가 명확한 경계를 가지므로, 코드 베이스의 모듈화 수준을 높이고 의존성을 명확하게 파악할 수 있습니다. 이는 아키텍처 설계와 유지보수에 도움이 됩니다.

코드 이동 및 재구성 용이

  • 논리적으로 분리된 하위 프로젝트 덕분에 코드 베이스를 리팩토링하거나 새로운 기능을 추가할 때 훨씬 유연하게 대처할 수 있습니다.

IDE 성능 향상

  • IDE는 전체 프로젝트가 아닌 현재 작업 중인 하위 프로젝트와 그 의존성만 분석하여, 자동 완성 및 타입 검사 성능을 향상시킬 수 있습니다.

프로젝트 레퍼런스 설정 방법

프로젝트 레퍼런스를 사용하려면, 의존성을 가지는 각 하위 프로젝트의 tsconfig.json 파일에 몇 가지 변경 사항을 적용해야 합니다.

기본 구조 예시 (모노레포)

tsconfig.json
types.ts
tsconfig.json)
package.json
tsconfig.json

1단계: common 프로젝트 (생산자) 설정

common 프로젝트는 다른 프로젝트가 의존할 타입 정의 파일(.d.ts) 을 생성해야 합니다.

my-monorepo/packages/common/tsconfig.json
{
  "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에 의해 참조될 수 있는 프로젝트임을 나타내는 플래그입니다. 이 옵션을 켜면 declarationdeclarationMap이 자동으로 true로 설정됩니다 (명시적으로 써주는 것이 좋습니다).

2단계: ui 프로젝트 (소비자) 설정

ui 프로젝트는 common 프로젝트가 제공하는 타입에 의존합니다. references 배열을 사용하여 이 의존성을 명시합니다.

my-monorepo/packages/ui/tsconfig.json
{
  "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은 일반적으로 직접 코드를 컴파일하기보다는, 하위 프로젝트들의 빌드 순서를 관리하고 증분 빌드를 트리거하는 역할을 합니다.

my-monorepo/tsconfig.json
{
  "files": [], // 이 tsconfig 자체는 컴파일할 파일이 없으므로 비워둡니다.
  "references": [
    { "path": "./packages/common" }, // common 프로젝트를 먼저 빌드해야 합니다.
    { "path": "./packages/ui" }      // ui 프로젝트는 common에 의존하므로 그 다음에 빌드됩니다.
  ]
}
  • "files": []: 이 루트 tsconfig.json은 직접 소스 파일을 컴파일하지 않고, 단지 하위 프로젝트들을 참조만 하므로 filesinclude를 비워두는 것이 일반적입니다.
  • "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.jsonpaths 매핑을 추가해야 합니다.

my-monorepo/packages/ui/tsconfig.json 수정

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을 "Solution tsconfig.json"이라고 부르기도 합니다.

프로젝트 레퍼런스는 대규모 타입스크립트 프로젝트, 특히 모노레포 환경에서 개발 효율성과 빌드 성능을 획기적으로 향상시킬 수 있는 강력한 기능입니다. 각 하위 프로젝트의 독립성을 유지하면서도, 타입 안전한 방식으로 서로 의존할 수 있도록 하여 복잡한 코드 베이스를 더 효과적으로 관리할 수 있게 합니다.