본문으로 건너뛰기
Advertisement

9.5 타입 체크 최적화 — 빠른 TypeScript 빌드

타입 체크가 느려지는 이유

대규모 TypeScript 프로젝트에서 tsc --noEmit이 수십 초 이상 걸린다면 아래 요인들을 점검해야 합니다.

원인영향해결책
전체 재컴파일높음incremental 빌드
라이브러리 타입 체크중간skipLibCheck
복잡한 타입 추론높음타입 단순화
많은 파일 포함중간include/exclude 최적화

incremental 빌드

가장 효과적인 최적화입니다. 변경된 파일만 재컴파일합니다.

{
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": ".tsbuildinfo" // 캐시 파일 위치
}
}
# 첫 번째 실행: 전체 컴파일 (느림)
tsc --noEmit
# .tsbuildinfo 파일 생성됨

# 두 번째 실행: 변경된 파일만 컴파일 (빠름)
tsc --noEmit

.gitignore에 추가

# TypeScript 빌드 캐시
*.tsbuildinfo

CI에서의 incremental 빌드

# .github/workflows/typecheck.yml
- name: Cache TypeScript build info
uses: actions/cache@v3
with:
path: .tsbuildinfo
key: tsbuildinfo-${{ hashFiles('**/*.ts', 'tsconfig.json') }}

- name: Type check
run: tsc --noEmit

skipLibCheck

node_modules.d.ts 파일 타입 체크를 건너뜁니다.

{
"compilerOptions": {
"skipLibCheck": true
}
}

효과:

  • 빌드 속도 20~50% 향상
  • 서드파티 라이브러리 타입 오류 무시

언제 사용하나?

✅ 권장: 거의 모든 프로젝트 (라이브러리 타입 오류는 @types 업데이트로 해결)
❌ 비권장: 라이브러리 개발 시 (타입 정확성 중요)

isolatedModules

각 파일을 독립적으로 트랜스파일 가능하도록 강제합니다. esbuild, swc 사용 시 필수입니다.

{
"compilerOptions": {
"isolatedModules": true
}
}

이 옵션은 성능보다는 번들러 호환성을 위한 옵션입니다. 단일 파일 트랜스파일 도구와의 호환성을 보장합니다.


include/exclude 최적화

컴파일할 파일 범위를 정확하게 설정합니다.

{
"compilerOptions": { },
"include": [
"src/**/*.ts",
"src/**/*.tsx"
],
"exclude": [
"node_modules",
"dist",
"**/*.test.ts", // 테스트 파일 제외 (별도 tsconfig 사용)
"**/*.spec.ts",
"**/__tests__/**",
"scripts/**" // 빌드 스크립트 제외
]
}

테스트용 별도 tsconfig

// tsconfig.test.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"types": ["vitest/globals", "@types/jest"]
},
"include": [
"src/**/*.ts",
"src/**/*.test.ts",
"tests/**/*.ts"
]
}

.tsbuildinfo 파일 이해

증분 빌드 캐시 파일입니다. TypeScript 4.0+에서 도입되었습니다.

// .tsbuildinfo (요약된 구조)
{
"program": {
"fileInfos": {
"src/index.ts": {
"version": "abc123", // 파일 해시
"signature": "xyz789" // 타입 시그니처 해시
}
},
"options": { },
"referencedMap": { },
"exportedModulesMap": { },
"semanticDiagnosticsPerFile": []
}
}

TypeScript는 이 파일을 참조해 어떤 파일이 변경되었는지 판단합니다.


복잡한 타입 단순화

타입 추론이 복잡할수록 컴파일 시간이 증가합니다.

명시적 반환 타입 추가

// ❌ 복잡한 추론 (느림)
function processUsers(users: User[]) {
return users
.filter(u => u.active)
.map(u => ({ ...u, displayName: `${u.firstName} ${u.lastName}` }))
.reduce((acc, u) => ({ ...acc, [u.id]: u }), {});
}

// ✅ 명시적 반환 타입 (빠름)
function processUsers(users: User[]): Record<string, ActiveUser> {
return users
.filter(u => u.active)
.map(u => ({ ...u, displayName: `${u.firstName} ${u.lastName}` }))
.reduce<Record<string, ActiveUser>>((acc, u) => ({ ...acc, [u.id]: u }), {});
}

복잡한 조건부 타입 캐싱

// ❌ 반복 계산 (느림)
type ExtractReturnTypes<T extends Record<string, (...args: any[]) => any>> = {
[K in keyof T]: ReturnType<T[K]>;
};

// ✅ 중간 타입으로 캐싱
type Fn = (...args: any[]) => any;
type FnRecord = Record<string, Fn>;
type ExtractReturnTypes<T extends FnRecord> = {
[K in keyof T]: ReturnType<T[K]>;
};

TypeScript 성능 진단

--diagnostics 플래그

tsc --noEmit --diagnostics

출력 예시:

Files:                         156
Lines of Library: 37956
Lines of Definitions: 11304
Lines of TypeScript: 8901
Lines of JavaScript: 0
Lines of JSON: 246
Lines of Other: 0
Identifiers: 70426
Symbols: 59387
Types: 9248
Instantiations: 12871
Memory used: 133286K
Assignability cache size: 9098
Identity cache size: 162
Subtype cache size: 57
Strict subtype cache size: 2499
I/O Read time: 0.13s
Parse time: 0.44s
ResolveModule time: 0.19s
ResolveLibrary time: 0.01s
ResolveTypeReference time: 0.00s
Bind time: 0.33s
Check time: 1.91s ← 주목
Emit time: 0.00s
Total time: 3.02s

Check time이 높다면 복잡한 타입 추론을 단순화해야 합니다.

--extendedDiagnostics 플래그

더 상세한 진단 정보를 출력합니다.

tsc --noEmit --extendedDiagnostics 2>&1 | grep "Check time"

실전 최적화 설정 예시

대규모 프로젝트 tsconfig.json

{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,

// 성능 최적화
"incremental": true,
"tsBuildInfoFile": ".cache/.tsbuildinfo",
"skipLibCheck": true,
"isolatedModules": true,

// 범위 최소화
"noEmit": true
},
"include": ["src"],
"exclude": [
"node_modules",
"dist",
".cache",
"**/*.test.*",
"**/__mocks__/**"
]
}

빌드 시간 측정 스크립트

# macOS/Linux
time tsc --noEmit

# 반복 측정
for i in 1 2 3; do time tsc --noEmit 2>&1; done

병렬 타입 체크

여러 TypeScript 프로젝트를 병렬로 체크합니다.

# tsc --build는 가능한 경우 병렬 빌드
tsc --build --verbose

GitHub Actions에서 병렬 실행

jobs:
typecheck:
strategy:
matrix:
package: [web, api, mobile]
steps:
- run: tsc --noEmit
working-directory: apps/${{ matrix.package }}

고수 팁

1. 개발 시 타입 체크 분리

// package.json
{
"scripts": {
"dev": "vite", // 번들링 (타입 체크 없음)
"typecheck": "tsc --noEmit", // 타입 체크만
"typecheck:watch": "tsc --noEmit --watch" // 변경 감지 타입 체크
}
}

2. VSCode 설정으로 오류 즉시 확인

// .vscode/settings.json
{
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}

3. 타입 체크 스킵 패턴 (긴급 우회)

// @ts-ignore — 다음 줄 오류 무시 (사유 설명 필수)
// @ts-ignore: 레거시 API, 추후 타입 정의 추가 예정
legacyFunction(data);

// @ts-expect-error — 오류가 있을 것으로 예상 (없으면 역으로 오류)
// @ts-expect-error: 의도적으로 잘못된 타입 테스트
const result: string = 42;

// @ts-nocheck — 파일 전체 타입 체크 무시 (마이그레이션 시 한시적 사용)
// @ts-nocheck
Advertisement