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