TypeScript Isolated Declarations: 모노레포 속도 향상시키기

모노레포 빌드는 TypeScript가 선언 파일(declaration files)을 처리하는 방식 때문에 느려지는 경우가 많습니다. 기본적으로 TypeScript는 단 하나의 .d.ts 파일을 생성하기 전에 모든 임포트 체인을 분석합니다. 이로 인해 패키지들이 서로를 기다려야 하는 병목 현상이 발생합니다.

--isolatedDeclarations 플래그를 사용하면 이 문제를 해결할 수 있습니다.

이 플래그는 병렬 빌드를 가능하게 합니다. 의존성을 기다리는 대신, 각 패키지가 독립적으로 자신의 선언을 생성합니다. 이를 통해 빌드 속도가 3배에서 15배까지 향상되는 것을 경험할 수 있습니다.

문제점: TypeScript는 보통 모듈 경계를 가로질러 타입을 추론합니다. 만약 패키지 A가 패키지 B에 의존한다면, 컴파일러는 A를 시작하기 전에 B를 완전히 해결(resolve)해야 합니다. 대규모 모노레포에서는 컴파일러가 하나씩 처리하는 동안 대부분의 패키지가 아무 작업도 하지 못한 채 대기하게 됩니다.

해결책: --isolatedDeclarations 플래그는 규칙을 바꿉니다. 이 플래그를 사용하면 내보내는(export) 모든 항목에 대해 명시적인 타입 어노테이션을 작성해야 합니다.

트레이드오프(Tradeoff): 코드의 양이 늘어납니다. 함수에는 반환 타입을, 상수에는 타입을 수동으로 추가해야 합니다. 컴파일러가 코드를 보고 타입을 추측하도록 맡길 수 없습니다.

결과: 모든 export에 명확한 타입이 지정되어 있기 때문에, 컴파일러는 이를 이해하기 위해 다른 패키지를 살펴볼 필요가 없습니다. 따라서 모든 패키지를 동시에 처리할 수 있습니다.

실제 사례 결과:

  • 18개의 패키지로 구성된 모노레포의 빌드 시간이 47초에서 3.2초로 단축되었습니다.
  • 32개의 패키지로 구성된 모노레포는 CI 파이프라인에서 8배의 성능 향상을 보였습니다.
  • 성능은 CPU 코어 수에 따라 확장됩니다. 코어가 많을수록 더 많은 패키지를 동시에 빌드할 수 있습니다.

마이그레이션 방법:

  1. 루트 tsconfig.json과 모든 패키지 설정에서 플래그를 활성화합니다.
  2. 프로젝트 참조(project references)를 허용하기 위해 "composite" 플래그를 사용합니다.
  3. 내보내는 함수에 명시적인 반환 타입을 추가합니다:
// 이전
export function add(a: number, b: number) {
  return a + b;
}

// 이후
export function add(a: number, b: number): number {
  return a + b;
}
  1. 내보내는 상수에 명시적인 타입을 추가합니다:
// 이전
export const SETTINGS = { port: 3000 };

// 이후
export const SETTINGS: { port: number } = { port: 3000 };

이러한 타입을 추가하지 않으면 컴파일러가 에러를 발생시킵니다. 이는 타입이 결정론적(deterministic)이고 빠르게 처리되도록 보장합니다.

Source: https://dev.to/jsmanifest/typescript-isolated-declarations-real-world-performance-gains-in-monorepo-build-times-25g3