본문 바로가기

증분 컴파일과 빌드 최적화

incremental이 .tsbuildinfo에 남기는 빌드 정보와 watch 모드, 빌드 모드의 차이를 정리하고, 빌드 정보가 검사를 건너뛰게 해 주지 않는다는 점을 실습으로 확인한 뒤, --extendedDiagnostics로 병목을 재고 검사와 변환을 나누는 방법으로 빌드를 줄이는 절차를 다룹니다.

프로젝트가 커지면 코드를 조금 고칠 때마다 전체를 다시 검사하고 출력하는 시간이 길어집니다. 대부분의 수정은 몇 개의 파일만 바꾸므로, 이전에 한 일을 기억해 두었다가 바뀐 부분과 그 영향을 받는 부분만 다시 처리하면 시간을 크게 줄일 수 있습니다. 이렇게 이전의 결과를 다시 쓰며 필요한 일만 하는 방식을 증분 컴파일(incremental compilation)이라고 합니다.

이 글은 컴파일러가 증분 컴파일을 위해 남기는 빌드 정보와, 그 정보를 쓰는 세 가지 실행 방식을 정리합니다. 이어서 빌드 정보가 검사를 건너뛰게 해 주는 것은 아니라는 점을 실습으로 확인하고, 빌드가 느릴 때 원인을 재고 줄이는 절차를 봅니다.

incremental과 빌드 정보

incremental을 켜면 컴파일러는 컴파일을 마칠 때 다음 컴파일이 다시 쓸 정보를 .tsbuildinfo 파일에 기록합니다. 그 안에는 각 파일의 내용을 나타내는 값과 파일 사이의 의존 관계, 공개된 타입의 모양, 이전의 진단 같은 정보가 들어 있습니다.

tsconfig.json
{
  "compilerOptions": {
    "incremental": true,
    "tsBuildInfoFile": "./.cache/build.tsbuildinfo"
  }
}

다음 컴파일은 이 파일을 읽어 지금의 소스와 비교하고, 내용이 바뀐 파일과 그 파일의 공개된 타입에 의존하는 파일만 다시 검사하고 출력합니다. 빌드 정보의 기본 위치는 outDir과 설정 파일의 위치에 따라 달라지므로, 위치를 고정하려면 tsBuildInfoFile을 적습니다. 이 파일은 컴파일러가 쓰는 캐시일 뿐 실행하는 코드가 읽는 파일이 아니므로, 보통 버전 관리에서 빼고 지워도 다음 컴파일이 다시 만듭니다.

빌드 정보를 쓰는 세 가지 방식

빌드 정보를 다시 쓰는 방법은 실행 방식에 따라 다릅니다.

방식명령다시 쓰는 것
증분 컴파일incremental을 켜고 tsc -p .실행과 실행 사이에 디스크에 남긴 .tsbuildinfo
감시 모드tsc -w실행 중인 프로세스가 메모리에 들고 있는 상태
빌드 모드tsc -b프로젝트마다의 .tsbuildinfo와 출력 파일의 시각

감시 모드(watch mode)는 한 번 실행한 뒤 끝나지 않고 파일의 변경을 지켜보다가, 파일을 저장할 때마다 바뀐 부분만 다시 컴파일합니다. 프로그램의 상태를 메모리에 들고 있으므로 매우 빠르게 반응하지만, 프로세스를 끄면 그 상태는 사라집니다. 빌드 모드는 앞 글의 프로젝트 레퍼런스에서 쓰며, composite 프로젝트는 incremental이 저절로 켜져서 프로젝트마다 빌드 정보를 남깁니다.

증분 빌드는 프로그램 그래프를 갱신하는 운영 루프다 — 상태 추적

이전 서명과 소스/옵션 변화, 필요한 검사/emit 및 .tsbuildinfo 상태가 반복 빌드에서 달라진다. 연결 MDX의 단일 프로젝트 6.0.3 실습으로 cache 존재와 오류/기존 산출물 보존을 구분할 가치가 있다.

변경 감지→이전 상태 비교→영향 계산→필요 작업→새 상태 기록이 순환한다. 공통 프로그램 그래프에 근거하며 watch 메모리와 incremental 디스크 상태를 구별한다.

CHANGE / GRAPH / REUSE

변경의 영향을 계산하고 필요한 작업 뒤 재사용 상태를 갱신한다

파일·옵션·산출물 상태와 이전 그래프를 비교해 필요한 검사·출력을 고릅니다. watch는 실행 중 상태를, incremental은 실행 사이 저장한 빌드 정보를 재사용합니다.

변경의 영향을 계산하고 필요한 작업 뒤 재사용 상태를 갱신한다변경 감지→이전 상태 비교→영향 계산→필요 작업→새 상태 기록이 순환한다. 공통 프로그램 그래프에 근거하며 watch 메모리와 incremental 디스크 상태를 구별한다.1 · 변경 감지파일 · 옵션2 · 상태 비교이전 서명3 · 영향 계산공개 선언 · 의존4 · 필요한 작업검사 · emit5 · 상태 기록다음 변경 재사용프로그램 그래프재사용 가능한 상태실선: 처리 순서와 다음 변경으로 반복
변경의 영향을 계산하고 필요한 작업 뒤 재사용 상태를 갱신한다변경 감지→이전 상태 비교→영향 계산→필요 작업→새 상태 기록이 순환한다. 공통 프로그램 그래프에 근거하며 watch 메모리와 incremental 디스크 상태를 구별한다.프로그램 그래프재사용 가능한공통 상태1 · 변경 감지파일 · 옵션2 · 상태 비교이전 서명3 · 영향 계산공개 선언 · 의존4 · 필요한 작업검사 · emit5 · 상태 기록다음 변경 재사용다음 변경으로 반복

DETECT

입력과 상태 변화

소스뿐 아니라 옵션·의존성·산출물·cache 상태도 판정에 영향을 줍니다. 같은 입력에서 작업을 재사용할 수 있는지 확인합니다.

AFFECTED

선언과 구현 변화의 영향

공개 선언이 바뀌면 소비자 검사에 영향을 줄 수 있습니다. 내부 구현만 바뀌고 선언이 같다면 소비자의 일부 작업을 재사용할 수 있습니다.

INCREMENTAL

실행 사이 디스크 재사용

incremental은 .tsbuildinfo를 저장합니다. 이 파일은 배포 산출물이 아니며 삭제해도 다시 만들 수 있는 cache입니다.

WATCH / BUILD

실행 중 감시와 프로젝트 빌드

tsc -w는 같은 프로세스에서 변경을 감시합니다. tsc -b는 프로젝트 참조 그래프의 오래된 작업을 의존 순서로 처리합니다.

cache는 정확성의 대체물이 아니라 검증된 입력에서 필요한 작업을 줄이는 상태입니다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
원도표의 전체 관계·조건·설명
구분내용
원도표 설명incremental은 .tsbuildinfo를 저장합니다. 이 파일은 배포 산출물이 아니며 삭제해도 다시 만들 수 있는 cache입니다.
원도표 설명tsc -w는 같은 프로세스에서 변경을 감시합니다. tsc -b는 프로젝트 참조 그래프의 오래된 작업을 의존 순서로 처리합니다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
성공 total 3진단: 성공 / 산출: dist/index.js지정한 6.0.3 구성으로 total:number=3을 검사한다incremental cache는 실행 프로그램의 import 대상이 아니다
캐시만 없음cache: 없음 / JS: 기존 파일 유지build.tsbuildinfo만 없어져도 기존 JS는 남고 다음 검사에서 cache를 재구성할 수 있다cache 부재가 자체 타입 오류를 뜻하지 않는다
잘못된 문자열 3진단: string→number 오류 / JS: 이전 파일 잔존total:number="3"는 진단을 만들고 noEmitOnError:true가 새 JS 방출을 막는다cache는 검사 면제권이 아니고 잔존 JS는 새 검사 성공 증거가 아니다
검사와 emit 정책검사: app 오류는 검사 / emit: noEmit이면 없음skipLibCheck로 해당 app 오류는 없어지지 않으며 고친 뒤 noEmit:true도 새 JS는 안 만든다cache/진단/새 산출/기존 파일의 네 관찰을 분리한다

증분 빌드는 프로그램 그래프를 갱신하는 운영 루프다 — 상태 추적

이전 서명과 소스/옵션 변화, 필요한 검사/emit 및 .tsbuildinfo 상태가 반복 빌드에서 달라진다. 연결 MDX의 단일 프로젝트 6.0.3 실습으로 cache 존재와 오류/기존 산출물 보존을 구분할 가치가 있다.

변경 감지→이전 상태 비교→영향 계산→필요 작업→새 상태 기록이 순환한다. 공통 프로그램 그래프에 근거하며 watch 메모리와 incremental 디스크 상태를 구별한다.

CHANGE / GRAPH / REUSE

변경의 영향을 계산하고 필요한 작업 뒤 재사용 상태를 갱신한다

파일·옵션·산출물 상태와 이전 그래프를 비교해 필요한 검사·출력을 고릅니다. watch는 실행 중 상태를, incremental은 실행 사이 저장한 빌드 정보를 재사용합니다.

변경의 영향을 계산하고 필요한 작업 뒤 재사용 상태를 갱신한다변경 감지→이전 상태 비교→영향 계산→필요 작업→새 상태 기록이 순환한다. 공통 프로그램 그래프에 근거하며 watch 메모리와 incremental 디스크 상태를 구별한다.1 · 변경 감지파일 · 옵션2 · 상태 비교이전 서명3 · 영향 계산공개 선언 · 의존4 · 필요한 작업검사 · emit5 · 상태 기록다음 변경 재사용프로그램 그래프재사용 가능한 상태실선: 처리 순서와 다음 변경으로 반복
변경의 영향을 계산하고 필요한 작업 뒤 재사용 상태를 갱신한다변경 감지→이전 상태 비교→영향 계산→필요 작업→새 상태 기록이 순환한다. 공통 프로그램 그래프에 근거하며 watch 메모리와 incremental 디스크 상태를 구별한다.프로그램 그래프재사용 가능한공통 상태1 · 변경 감지파일 · 옵션2 · 상태 비교이전 서명3 · 영향 계산공개 선언 · 의존4 · 필요한 작업검사 · emit5 · 상태 기록다음 변경 재사용다음 변경으로 반복

DETECT

입력과 상태 변화

소스뿐 아니라 옵션·의존성·산출물·cache 상태도 판정에 영향을 줍니다. 같은 입력에서 작업을 재사용할 수 있는지 확인합니다.

AFFECTED

선언과 구현 변화의 영향

공개 선언이 바뀌면 소비자 검사에 영향을 줄 수 있습니다. 내부 구현만 바뀌고 선언이 같다면 소비자의 일부 작업을 재사용할 수 있습니다.

INCREMENTAL

실행 사이 디스크 재사용

incremental은 .tsbuildinfo를 저장합니다. 이 파일은 배포 산출물이 아니며 삭제해도 다시 만들 수 있는 cache입니다.

WATCH / BUILD

실행 중 감시와 프로젝트 빌드

tsc -w는 같은 프로세스에서 변경을 감시합니다. tsc -b는 프로젝트 참조 그래프의 오래된 작업을 의존 순서로 처리합니다.

cache는 정확성의 대체물이 아니라 검증된 입력에서 필요한 작업을 줄이는 상태입니다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.

관계도의 보충 설명

원도표의 전체 관계·조건·설명
구분내용
원도표 설명incremental은 .tsbuildinfo를 저장합니다. 이 파일은 배포 산출물이 아니며 삭제해도 다시 만들 수 있는 cache입니다.
원도표 설명tsc -w는 같은 프로세스에서 변경을 감시합니다. tsc -b는 프로젝트 참조 그래프의 오래된 작업을 의존 순서로 처리합니다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
성공 total 3진단: 성공 / 산출: dist/index.js지정한 6.0.3 구성으로 total:number=3을 검사한다incremental cache는 실행 프로그램의 import 대상이 아니다
캐시만 없음cache: 없음 / JS: 기존 파일 유지build.tsbuildinfo만 없어져도 기존 JS는 남고 다음 검사에서 cache를 재구성할 수 있다cache 부재가 자체 타입 오류를 뜻하지 않는다
잘못된 문자열 3진단: string→number 오류 / JS: 이전 파일 잔존total:number="3"는 진단을 만들고 noEmitOnError:true가 새 JS 방출을 막는다cache는 검사 면제권이 아니고 잔존 JS는 새 검사 성공 증거가 아니다
검사와 emit 정책검사: app 오류는 검사 / emit: noEmit이면 없음skipLibCheck로 해당 app 오류는 없어지지 않으며 고친 뒤 noEmit:true도 새 JS는 안 만든다cache/진단/새 산출/기존 파일의 네 관찰을 분리한다

위 그림은 이 과정을 하나의 순환으로 보여 줍니다. 변경을 감지하면 이전 상태와 비교해 영향의 범위를 계산하고, 필요한 검사와 출력만 한 뒤 새 상태를 기록합니다. 아래의 실습은 그림의 각 단계에서 빌드 정보와 출력 파일, 진단이 어떻게 달라지는지 따라갑니다.

빌드 정보는 검사를 건너뛰게 하지 않는다

새 폴더 ts-incremental에 package.json을 { "type": "commonjs" }로 두고, 다음 설정과 파일을 만듭니다. 설정에서 incremental은 컴파일할 때마다 검사 결과를 빌드 정보 파일에 남기게 하고, tsBuildInfoFile은 그 파일을 출력 폴더가 아닌 .cache에 두게 합니다. noEmitOnError가 켜져 있으므로 진단이 하나라도 있으면 dist에 새 파일을 쓰지 않습니다.

tsconfig.json
{
  "compilerOptions": {
    "target": "es2022",
    "module": "nodenext",
    "rootDir": "./src",
    "outDir": "./dist",
    "strict": true,
    "incremental": true,
    "tsBuildInfoFile": "./.cache/build.tsbuildinfo",
    "noEmitOnError": true
  },
  "include": ["src/**/*.ts"]
}
src/index.ts
export const total: number = 3;

tsc -p .을 실행하면 진단 없이 끝나고, dist/index.js와 .cache/build.tsbuildinfo가 생깁니다. 이제 이 상태에서 세 가지 변화를 차례로 일으켜 봅니다.

먼저 .cache/build.tsbuildinfo만 지우고 다시 컴파일합니다. 빌드 정보는 실행에 쓰이지 않으므로 dist/index.js는 그대로 쓸 수 있고, 다음 컴파일은 정보가 없으니 처음부터 검사한 뒤 빌드 정보를 다시 만듭니다. 이때의 컴파일은 빌드 정보를 다시 쓴 컴파일보다 느리므로, 시간을 잴 때 두 경우를 섞어 비교하지 않습니다.

다음으로 소스를 export const total: number = "3";으로 바꾸고 다시 컴파일합니다. 빌드 정보는 바뀐 파일을 다시 검사하기 위한 것이지 검사를 면제해 주지 않으므로, Type 'string' is not assignable to type 'number'.가 그대로 나옵니다. noEmitOnError 때문에 새 dist/index.js는 만들어지지 않지만, 처음 컴파일에서 만든 파일은 지워지지 않고 남습니다.

마지막으로 같은 오류가 있는 상태에서 skipLibCheck: true를 더해 봅니다. 이 옵션은 선언 파일의 검사를 건너뛸 뿐이어서, src/index.ts의 대입 오류는 사라지지 않습니다. 오류를 고친 뒤 noEmit: true로 바꾸면 검사는 성공하지만, 이 옵션은 출력을 하지 않기로 정한 것이므로 새 자바스크립트도 만들어지지 않습니다. 남아 있는 출력 파일과 마지막 검사의 성공 여부는 따로 확인해야 합니다.

빌드 시간 재기

빌드가 느리다고 느껴지면, 옵션을 바꾸기 전에 어디에서 시간이 드는지부터 잽니다. tsc -p . --extendedDiagnostics는 컴파일을 마친 뒤 읽은 파일의 개수와 줄 수, 구문 분석과 검사, 출력에 각각 걸린 시간, 쓴 메모리를 출력합니다. 프로그램에 생각보다 많은 파일이 들어 있다면 --explainFiles로 각 파일이 왜 들어왔는지 확인하고, 검사가 느린 타입을 찾으려면 --generateTrace 폴더에 성능 기록을 남겨 분석합니다.

기준선→재현된 병목→한 요소 변경→시간과 결과 검증의 순서다. 재현되지 않으면 환경과 캐시 조건부터 확인하며 이 그림은 벤치마크 결과가 아니다.

MEASURE → CHANGE → VERIFY

측정한 병목에 한 번의 변경을 적용하고 다시 검증한다

type-check·transpile·bundle·test를 분리해 cold와 warm, 로컬과 CI를 같은 조건으로 측정합니다. 병목이 재현되면 관련된 한 요소를 바꾸고 시간과 결과 정확성을 함께 비교합니다.

측정한 병목에 한 번의 변경을 적용하고 다시 검증한다기준선→재현된 병목→한 요소 변경→시간과 결과 검증의 순서다. 재현되지 않으면 환경과 캐시 조건부터 확인하며 이 그림은 벤치마크 결과가 아니다.
1 · 단계별 기준선cold / warm · 로컬 / CI
연결 관계1 · 단계별 기준선 · cold / warm · 로컬 / CI → 2 · 병목 재현 확인 · 검사 · 변환 · bundle · test
2 · 병목 재현 확인검사 · 변환 · bundle · test
연결 관계2 · 병목 재현 확인 · 검사 · 변환 · bundle · test → 3 · 관련 요소 하나 변경 · 범위 · cache · tool 역할
3 · 관련 요소 하나 변경범위 · cache · tool 역할
연결 관계3 · 관련 요소 하나 변경 · 범위 · cache · tool 역할 → 4 · 시간 + 결과 검증 · 같은 구성 · 반복 측정
4 · 시간 + 결과 검증같은 구성 · 반복 측정
측정한 병목에 한 번의 변경을 적용하고 다시 검증한다기준선→재현된 병목→한 요소 변경→시간과 결과 검증의 순서다. 재현되지 않으면 환경과 캐시 조건부터 확인하며 이 그림은 벤치마크 결과가 아니다.
1 · 단계별 기준선cold / warm로컬 / CI
연결 관계1 · 단계별 기준선 · cold / warm · 로컬 / CI → 2 · 병목 재현 · 검사 · 변환 · bundle · test
2 · 병목 재현검사 · 변환bundle · test
연결 관계2 · 병목 재현 · 검사 · 변환 · bundle · test → 3 · 한 요소 변경 · 범위 · cache · 도구 역할
3 · 한 요소 변경범위 · cache도구 역할
연결 관계3 · 한 요소 변경 · 범위 · cache · 도구 역할 → 4 · 시간과 결과 · 동일 구성 · 반복 측정
4 · 시간과 결과동일 구성반복 측정

BASELINE

캐시 조건을 명시한다

cold는 재사용 상태 없이, warm은 같은 조건의 재사용 상태로 비교합니다. tool/version·입력·옵션·machine·CI 조건과 cache hit를 함께 기록합니다.

ROUTE

재현되지 않으면 측정을 점검

한 번 빠르거나 느린 결과만으로 최적화를 선택하지 않습니다. 환경·입력·캐시 차이를 먼저 설명하고 같은 병목이 반복되는지 확인합니다.

CHANGE

병목의 책임에 맞춘다

검사 범위, project references, 증분 재사용, 변환 도구를 해당 단계에 맞춰 검토합니다. transpiler를 바꿔도 별도의 TypeScript 타입 검사는 필요합니다.

VERIFY

정확성과 속도를 같이 비교

같은 지원 구성에서 변경 전후의 진단·JS·선언·테스트 결과와 시간을 비교합니다. skipLibCheck처럼 오류를 숨길 수 있는 절충도 기록합니다.

이 그림은 측정 절차입니다. 교재에서 수행한 벤치마크 수치가 아닙니다.

위 그림은 이 측정의 절차를 보여 주며, 이 글이 잰 벤치마크 결과가 아닙니다. 먼저 빌드 정보가 없는 처음 상태와 있는 상태를 나누어 기준이 되는 시간을 재고, 타입 검사와 변환, 번들링, 테스트 가운데 어느 단계가 느린지 가립니다. 느린 단계가 다시 재어도 같게 나타나면 관련된 요소 하나만 바꾸고, 시간과 함께 결과가 여전히 정확한지 확인합니다. 같은 입력과 컴파일러 버전, 옵션, 실행 환경에서 잰 시간끼리만 비교해야 합니다.

검사와 변환 나누기

tsc는 타입 검사와 자바스크립트로의 변환을 함께 하지만, esbuild와 SWC, Babel 같은 도구는 타입을 지우는 변환만 하므로 훨씬 빠릅니다. Vite도 .ts 파일을 변환만 하고 타입 검사는 하지 않습니다. 그래서 큰 프로젝트에서는 변환과 번들링을 이런 도구에 맡기고, tsc는 noEmit으로 타입 검사만 하도록 역할을 나누는 경우가 많습니다.

이렇게 나눌 때는 파일을 하나씩 변환하는 도구가 틀리게 처리할 수 있는 문법을 미리 막도록 isolatedModules나 verbatimModuleSyntax를 켭니다. 그리고 빠른 도구는 타입 오류가 있어도 변환에 성공하므로, 타입 검사를 개발 중의 편집기에만 맡기지 말고 CI 같은 정해진 단계에서 반드시 실행합니다.

그 밖에 types로 전역에 포함할 @types 패키지를 줄이거나, skipLibCheck로 선언 파일의 검사를 건너뛰는 방법도 시간을 줄입니다. 다만 skipLibCheck는 선언 파일끼리 부딪히는 진단까지 숨기므로, 줄어든 시간이 검사의 범위를 줄인 대가라는 점을 함께 기록합니다. 2026년 7월에 나온 TypeScript 7.0은 컴파일러를 네이티브 코드로 다시 구현해 빌드 자체를 빠르게 했습니다. 6.0에서 폐기 예정인 옵션들은 6.0에서도 오류로 보고되지만 "ignoreDeprecations": "6.0"으로 잠시 끌 수 있었는데, 7.0에서는 그렇게 끌 수 없습니다.

더 깊이: TypeScript 7.0의 병렬 빌드

TypeScript 7.0 발표에 따르면, 7.0은 구문 분석과 타입 검사, 출력을 여러 작업으로 나누어 병렬로 실행합니다. 타입 검사 작업자의 기본 개수는 4이고, 실험 단계의 --checkers와 --builders로 병렬 처리를 조정하거나 --singleThreaded로 병렬 처리를 아예 끌 수 있습니다. 발표에 실린 측정에서 vscode 저장소의 빌드는 125.7초에서 10.6초로, tldraw는 11.2초에서 1.46초로 줄었고, vscode의 최대 메모리 사용량도 5.2GB에서 4.2GB로 줄었습니다. 감시 모드도 Parcel 번들러의 파일 감시기를 Go로 옮긴 기반 위에 새로 만들어졌습니다.

병렬 처리에는 따로 풀어야 할 문제가 있었습니다. 여러 검사기가 노드와 타입을 서로 다른 순서로 방문하면, 컴파일러가 안에서 붙이는 번호가 실행할 때마다 달라질 수 있기 때문입니다. 그래서 7.0은 같은 입력 파일을 언제나 같은 방식으로 나누고, 타입의 순서를 안정적으로 정하는 stableTypeOrdering을 항상 켜 둡니다. 6.0에도 같은 순서를 흉내 내는 --stableTypeOrdering 옵션이 있어 두 버전의 출력과 진단을 비교하며 옮겨 갈 때 쓸 수 있지만, 6.0 릴리스 노트는 이 옵션이 검사를 최대 25%까지 느리게 할 수 있으니 늘 켜 두는 것은 권하지 않습니다.

정리

  • incremental은 다음 컴파일이 다시 쓸 정보를 .tsbuildinfo에 남기며, 이 파일은 실행에 쓰이지 않는 캐시여서 지워도 다시 만들어집니다.
  • 증분 컴파일은 디스크의 빌드 정보를, 감시 모드는 실행 중인 프로세스의 메모리를, 빌드 모드는 프로젝트마다의 빌드 정보를 다시 씁니다.
  • 빌드 정보는 바뀐 파일을 다시 검사하게 할 뿐 검사를 면제하지 않으며, noEmitOnError는 남아 있는 예전 출력을 지우지 않습니다.
  • 빌드가 느리면 --extendedDiagnostics와 --explainFiles, --generateTrace로 원인을 잰 뒤 한 요소씩 바꾸고, 같은 조건에서 잰 시간끼리 비교합니다.
  • 변환은 빠른 도구에 맡기고 tsc는 noEmit으로 검사만 하도록 나눌 수 있지만, 그때는 정해진 단계에서 타입 검사를 반드시 실행합니다.

연습 문제

1. 시간만 비교한 최적화

어떤 사람이 skipLibCheck: true를 켠 뒤 빌드 시간이 30퍼센트 줄었다며 최적화가 끝났다고 보고했습니다. 이 보고에 무엇이 빠져 있나요?

해설 보기

시간이 줄어든 이유가 선언 파일의 검사를 하지 않게 된 것이므로, 검사의 범위가 줄었다는 사실이 함께 기록되어야 합니다. 또 두 시간이 같은 입력과 버전, 같은 빌드 정보의 상태에서 잰 것인지도 알려야 합니다. 빌드 정보가 있는 상태와 없는 상태를 섞어 비교하면, 옵션과 관계없이 시간이 크게 다르게 나올 수 있기 때문입니다.

2. 빠른 변환 도구와 타입 오류

esbuild로 변환하고 tsc는 따로 실행하지 않는 프로젝트에서, const count: number = "3";이 들어간 코드를 빌드하면 어떻게 되나요?

해설 보기

esbuild는 타입 표기를 지우기만 하므로 빌드는 성공하고, 출력에는 const count = "3";이 들어갑니다. 타입 오류는 어디서도 보고되지 않으므로, 그 값을 숫자로 믿고 쓰는 코드가 실행 중에 엉뚱하게 동작할 수 있습니다. 그래서 변환과 검사를 나눈 프로젝트는 tsc --noEmit을 CI 같은 정해진 단계에서 함께 실행해야 합니다.

3. 감시 모드를 다시 켰을 때

tsc -w를 켜 두고 파일 하나를 고칠 때마다 빠르게 다시 컴파일되는 것을 확인했습니다. 프로세스를 끄고 다시 켰더니 첫 컴파일은 오래 걸렸습니다. 왜 그런가요? 다시 켤 때도 빠르게 하려면 무엇을 켜야 하나요?

해설 보기

감시 모드는 프로그램의 상태를 실행 중인 프로세스의 메모리에 들고 있으므로, 프로세스를 끄면 그 상태가 사라지고 다시 켤 때 처음부터 검사합니다. incremental을 함께 켜 두면 실행을 마칠 때 빌드 정보를 디스크에 남기므로, 감시 모드를 다시 켤 때도 그 정보를 읽어 바뀐 부분만 처리할 수 있습니다.

이것으로 9장의 컴파일러를 마칩니다. 다음 장의 SOLID 원칙 적용에서는 타입스크립트의 클래스와 인터페이스로 객체지향 설계의 다섯 가지 원칙을 적용해 봅니다.