타입 스크립트 컴파일 시 declaration 옵션을 키면 컴파일 속도가 빨라질까?
최근 빌더 개선 작업을 하고있는데 통한 컴파일 시 타입 진단 작업이 너무 오래걸려 해당 시간을 줄이기 위해 가설을 하나 세웠다. 용량이 큰 모듈의 tsconfig에서 declaration 옵션을 켜서 컴파일 시 .d.ts 파일을 생성하게 해주고 이후 타입 진단 과정에서 이를 참조해서 타입을 진단하면 컴파일 속도가 빨라질 수도 있다, 그리고 이는 d.ts 파일을 생성하는데 걸리는 시간과 참조 타입을 사용하여 감소하는 시간을 적절히 고려해야한다는 가설이다. 이 글은 이 가설을 검증하기 위한 글이다.
가설
- 큰 디렉토리를 컴파일할 때 해당 디렉토리에 d.ts 파일을 추가해 컴파일하면 d.ts 파일을 생성한 뒤 타입 진단을 진행하기에 해당 디렉토리의 컴파일 속도가 빨라진다.
- A 디렉토리의 파일을 많이 사용하는 B 디렉토리를 컴파일 할 때 A 디렉토리를 컴파일 할 때 d.ts 파일을 생성해주면 B 디렉토리 컴파일 시 B 디렉토리의 컴파일 속도가 빨라진다.
가설 검증에 앞서 먼저 타입스크립트의 .d.ts 파일이 무엇인지에 대해 알아보자.
.d.ts
.d.ts 파일이란?
.d.ts 파일은 TypeScript에서 타입 정보를 정의하는 파일이다. 이 파일에는 실제 구현 코드는 없고, 함수, 클래스, 변수 등의 타입 정보만 포함되어 있다. 주로 다음과 같은 목적으로 사용된다.
- 타입 정보 공유: 프로젝트 간 또는 모듈 간 타입 정보를 명확하게 공유할 수 있다.
- 외부 라이브러리 사용: JavaScript로 작성된 외부 라이브러리의 타입 정보를 제공하여 TypeScript 프로젝트에서 안전하게 사용할 수 있게 해준다.
- IDE 지원 향상: 코드 자동 완성, 인텔리센스, 타입 검사 등의 기능을 개선해준다.
.d.ts 파일의 생성
.d.ts 파일은 직접 생성하여 사용할수도 있지만 여기서는 tsc 과정을 개선해야 하기에 TypeScript 컴파일러(tsc)의 자동 생성을 기준으로 설명한다. 컴파일 시 d.ts 파일을 자동으로 생성해주게 하기 위해서는tsconfig.json 파일에 설정을 추가해야 한다.
tsconfig.json 설정 예시:
{
"compilerOptions": {
"declaration": true,
"declarationDir": "./types",
"emitDeclarationOnly": true},
"include": ["src/**/*"]
}
위 설정에서 declaration 옵션을 true로 설정하면, tsc는 컴파일 시 각 소스 파일에 대응하는 .d.ts 파일을 생성해준다. declarationDir은 생성된 선언 파일이 저장될 디렉토리를 지정한다.
명령어 실행:
tsc
위 명령어를 실행하면, 프로젝트의 타입 정의에 따라 .d.ts 파일이 자동으로 생성된다.
.d.ts 파일 생성의 이점
.d.ts 파일을 생성하는 것은 컴파일 시간을 줄이기 위한 것이 아니라, 타입 정보를 명확하게 하고 코드의 재사용성과 유지 보수성을 높이기 위한 것이다. 또한, 다음과 같은 이점을 제공한다
- 타입 안전성 보장: 외부 라이브러리나 모듈의 타입 정보를 명확히 정의하여 타입 안전성을 높여준다.
- 개발 생산성 향상: IDE에서 코드 자동 완성 및 타입 검사가 원활하게 이루어져 개발 생산성을 높입니다.
먼저 첫 번째 가설을 검증하기 위해 타입스크립트가 컴파일 시 내부적으로 타입 체크를 어떤식으로 하는지 알아야한다.
타입 진단은 타입스크립트를 import 한 뒤 getPreEmitDiagnostics 함수로 진단 가능하며 해당 함수가 어떻게 동작하는지만 알면 가설 검증이 가능하다.
이 함수 컴파일시 모든 타입스크립트 파일 대상으로 실행되어야 한다. 모든 타입스크립트 파일은 ts.createProgram 를 사용해 직접 프로그램을 생성한 뒤 해당 프로그램에서 getSourceFiles 을 사용해 직접 진단 함수에 넣어주거나 개발 방식에 따라 타입에 맞게 직접 구현해도 되고 그냥 tsc를 사용해서 쉽게 만들어도 된다.
진단 함수 살펴보기
아래는 진단을 수집하는 주요 함수인 getPreEmitDiagnostics의 코드이다.
export function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[];
/** @internal */ export function getPreEmitDiagnostics(program: BuilderProgram, sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; // eslint-disable-line @typescript-eslint/unified-signatures
export function getPreEmitDiagnostics(program: Program | BuilderProgram, sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] {
let diagnostics: Diagnostic[] | undefined;
diagnostics = addRange(diagnostics, program.getConfigFileParsingDiagnostics());
diagnostics = addRange(diagnostics, program.getOptionsDiagnostics(cancellationToken));
diagnostics = addRange(diagnostics, program.getSyntacticDiagnostics(sourceFile, cancellationToken));
diagnostics = addRange(diagnostics, program.getGlobalDiagnostics(cancellationToken));
diagnostics = addRange(diagnostics, program.getSemanticDiagnostics(sourceFile, cancellationToken));
if (getEmitDeclarations(program.getCompilerOptions())) {
diagnostics = addRange(diagnostics, program.getDeclarationDiagnostics(sourceFile, cancellationToken));
}
return sortAndDeduplicateDiagnostics(diagnostics || emptyArray);
}
이 함수는 컴파일 과정에서 다양한 종류의 진단 메시지를 수집하여 반환한다. 옵션, 문법, 타입 등 문제가 생길 수 있는 모든 경우에 대해 수집하여 반환해주며 타입 컴파일 시 많은 시간을 잡아먹는 함수이다.
진단 수집 과정
이제 각 진단 함수가 하는 일을 자세히 살펴보자
1.설정 파일 파싱 진단 (getConfigFileParsingDiagnostics)
diagnostics = addRange(diagnostics, program.getConfigFileParsingDiagnostics());
program.getConfigFileParsingDiagnostics()는 tsconfig.json과 같은 설정 파일을 파싱하는 동안 발생한 진단 메시지를 수집한다. 이 과정에서 설정 파일의 구문 오류나 잘못된 설정 값 등을 검출해준다.
2.옵션 진단 (getOptionsDiagnostics)
diagnostics = addRange(diagnostics, program.getOptionsDiagnostics(cancellationToken));
program.getOptionsDiagnostics(cancellationToken)는 컴파일러 옵션과 관련된 진단 메시지를 수집한다. 예를 들어, 잘못된 컴파일러 옵션 값이나 상호 배타적인 옵션 조합 등이 있을 수 있다.
3.구문 진단 (getSyntacticDiagnostics)
diagnostics = addRange(diagnostics, program.getSyntacticDiagnostics(sourceFile, cancellationToken));
program.getSyntacticDiagnostics(sourceFile, cancellationToken)는 소스 파일의 구문적(Syntactic)인 진단 메시지를 수집한다. 예를 들어, 잘못된 문법, 누락된 구문 요소 등이 포함된다.
4.전역 진단 (getGlobalDiagnostics)
diagnostics = addRange(diagnostics, program.getGlobalDiagnostics(cancellationToken));
program.getGlobalDiagnostics(cancellationToken)는 프로그램 전체에 영향을 미치는 전역적인 진단 메시지를 수집한다. 예를 들어, 전역 변수 충돌이나 전역 타입 선언 문제 등이 있을 수 있다.
5. 의미 진단 (getSemanticDiagnostics)
diagnostics = addRange(diagnostics, program.getSemanticDiagnostics(sourceFile, cancellationToken));
program.getSemanticDiagnostics(sourceFile, cancellationToken)는 의미적(Semantic)인 진단 메시지를 수집한다. 예를 들어, 타입 불일치, 사용되지 않는 변수, 잘못된 함수 호출 등이 포함된다.
6. 선언 파일 진단 (getDeclarationDiagnostics)
if (getEmitDeclarations(program.getCompilerOptions())) {
diagnostics = addRange(diagnostics, program.getDeclarationDiagnostics(sourceFile, cancellationToken));
}
컴파일러 옵션에서 선언 파일 생성을 설정한 경우(getEmitDeclarations가 true를 반환하는 경우), program.getDeclarationDiagnostics(sourceFile, cancellationToken)를 호출하여 선언 파일과 관련된 진단 메시지를 수집한다. 이는 선언 파일 생성 과정에서 발생할 수 있는 모든 문제를 포함한다.
7. 정렬 및 중복 제거
수집된 모든 진단 메시지는 최종적으로 sortAndDeduplicateDiagnostics 함수를 통해 정렬되고 중복이 제거되어 반환된다.
return sortAndDeduplicateDiagnostics(diagnostics || emptyArray);
가설 1 결론
declaration 또는 composite 옵션을 활성화하면 typeScript 컴파일러가 .d.ts 파일을 생성하지만, 이는 대상 모듈의 컴파일 속도에 영향을 미치지 않는다. 오히려 해당 옵션을 사용할 경우 .d.ts 파일을 생성하는데 걸리는 시간, .d.ts 생성 과정에서 진단에 걸리는 시간이 추가되기에 컴파일 시간이 증가하게 되며 벤치마킹 결과 1만개정도의 파일이 있는 디렉토리 기준으로 약 20~30%의 지연이 있었다.
따라서 가설 1은 잘못되었으며 가설2는 가설 1과 함께 적용되어야 하기에 확실한 컴파일 속도 개선이 있어야 가설 1의 .d.ts 파일 생성 시간을 보완할 수 있을것이다.
가설 2 검증
가설 2 검증을 위해서는 먼저 A 디렉토리의 파일들을 많이 import하여 사용하고 있는 B 디렉토리가 타입 체크 시 A 디렉토리의 js 혹은 ts파일을 보는 것이 아니라 .d.ts 파일을 보고 타입 체크를 진행하도록 변경해줘야 하며 A 디렉토리는 .d.ts 파일이 생성된 상태로 컴파일 되어있어야 한다. 따라서 .d.ts 파일의 생성 시간까지 고려해야 가설 2가 유효한지를 검증할 수 있다.
먼저 B 디렉토리가 A 디렉토리의 .d.ts 파일을 바라볼 수 있도록 tsconfig를 수정해준다. A 디렉토리의 원본 소스 경로를 가리키는게 아니라 A 디렉토리의 컴파일 결과물 경로를 추가해주면 .d.ts 파일을 바라보게된다.
이 때 주의할점은 항상 .d.ts 파일을 바라보면 개발시에 불편하기에 컴파일시에만 바라볼 수 있도록 컴파일, 개발의 tsconfig 파일을 분리해주는 것이 좋다.
이후 B 디렉토리를 컴파일 해서 벤치마킹 테스트를 진행해본 결과 약 70~80%의 컴파일 시간 감소라는 유의미한 결과 값을 얻을 수 있었다. B 디렉토리의 크기가 크면 클수록 더 효율이 좋을것이기에 이를 기반으로 빌더를 개선하기로 결정하였다.
결론적으로 A 디렉토리와 B 디렉토리의 .d.ts 파일 생성 시간, 컴파일 감소 시간을 잘 고려하여 컴파일러 성능 개선을 이뤄낼 수 있었다.
'typescript' 카테고리의 다른 글
타입 스크립트 5.5 업데이트 모든 내용 요약 (3) | 2024.07.22 |
---|---|
타입스크립트에서 타입 호환성은 어떻게 체크되는가? (1) | 2024.03.13 |
타입스크립트의 제네릭이란 (1) | 2024.03.07 |