(번역) 타입스크립트 컴파일러가 컴파일하는 방법

sehyun hwang·2022년 6월 7일
25

FE 번역글

목록 보기
9/36
post-thumbnail

원문 : https://www.huy.rocks/everyday/04-01-2022-typescript-how-the-compiler-compiles

이 글은 타입스크립트 컴파일러는 어떻게 컴파일하는가 에 대한 논의에서 영감을 받아 작성되었습니다. 타입스크립트의 컴파일링 과정에 대해 더 깊게 이해하려면 꼭 확인해보시기 바랍니다.

크게 보면, 타입스크립트 컴파일러는 타입스크립트 코드를 분석하여 타입 정의 파일(.d.ts) 또는 소스 맵(.js.map)과 함께 자바스크립트(*.js)로 컴파일하는 도구입니다.

만약 소스 파일에 문제가 있다면, 타입스크립트 컴파일러는 문제를 진단하여 우리에게 무엇이 잘못되었고 어떻게 고칠 수 있는지를 알려줍니다.

1. 컴파일 과정

컴파일은 내부적으로 많은 부분이 관여하는 복잡한 과정입니다. 다음은 컴파일 과정에 대한 모식도입니다.

overall process

tsc 명령어를 입력하면 컴파일 과정이 시작됩니다. 이를 실행하려면 타입스크립트 컴파일러는 tsconfig.json 파일이 필요합니다. 이 파일은 핵심적인 두 부분인 Compiler OptionsInput Files 를 정의합니다.

{
    "files": [
        "src/*.ts"
    ],
    "compilerOptions": {
        ...
    }
}

컴파일 컨텍스트는 src/compiler/program.ts 파일에 정의되어있는 Program 객체로 생성됩니다. 생성된 후에는 모든 입력 파일과 import를 로드합니다. 그리고 각각의 파일을 AST(Abstract Syntax Tree)로 변환하기 위해 Parser(src/compiler/parser.ts에 정의)를 호출합니다.

AST

내부적으로 ParserScanner(src/compiler/scanner.ts에 정의) 인스턴스를 생성합니다. 이 인스턴스는 소스 코드를 스캔하고 SyntaxKind 토큰 스트림을 생성합니다.

파싱 과정은 여기서 끝나지 않습니다. 이후 Binder(src/compiler/binder.ts에 정의)는 AST를 제공받아 AST NodeSymbol 사이에 맵을 생성합니다.

Symbol table

Symbol은 각 Node의 타입 정보를 저장하는 추가적인 메타데이터입니다. Binder는 타입 검사와 같이 나중 단계에서 활용될 Symbol Table을 생성합니다.

이후 Program.emit이 호출되면 AST를 자바스크립트 소스 코드 및 기타 항목의 문자열로 변환하기 위해 Emit Worker가 생성됩니다. Emitter에는 2가지 종류가 있습니다.

  • 자바스크립트 Emitter : src/compiler/emitter.ts에 정의되어있고, 자바스크립트 소스 코드와 소스 맵을 생성합니다.
  • 타입 정의 Emitter : src/compiler/definitionEmitter.ts에 정의되어있고, 타입 정의 파일을 생성합니다.

Emitter가 실행될 때, Type Checker(src/compiler/checker.ts에 정의)를 생성하기 위해 getDiagnostics() 함수를 호출합니다. 그런 뒤 Emitter는 각각의 Node를 처리하기 위해 AST를 순회합니다.

Node에서 Symbols Table의 타입 데이터를 이용하여 코드를 분석하고, 모든 과정이 잘 진행되었다면 마지막으로 자바스크립트 소스가 생성됩니다.

JS source generated

2. 에러 보고

컴파일러가 오류를 어느 단계에서 발견했는지에 따라서 컴파일 과정에서 여러 종류의 에러가 반환됩니다.

enum BuildResultFlags {
    None = 0,
    Success = 1 << 0,
    DeclarationOutputUnchanged = 1 << 1,
 
    ConfigFileErrors = 1 << 2,
    SyntaxErrors = 1 << 3,
    TypeErrors = 1 << 4,
    DeclarationEmitErrors = 1 << 5,
    EmitErrors = 1 << 6,
 
    AnyErrors = ConfigFileErrors | SyntaxErrors | TypeErrors | DeclarationEmitErrors | EmitErrors
}

예를 들어, tsconfig.json 파일에 에러가 있다면 ConfigFileErrors가 반환될 것입니다.

만약 Scanner에 의해 에러가 발견되었다면, SyntaxErrors가 반환될 것입니다. 때때로 코드가 올바른 구문으로 작성되었지만 의미상으로 올바르지 않은 경우가 있습니다. 이 경우 대부분 TypeErrors 가 발생합니다. 이는 Parser 또는 Type Checker에 의해 발견됩니다. TypeErrors 에러가 발생하는 예시는 아래와 같습니다.

let a: number = "hello";

이 코드는 올바른 구문으로 작성되었지만, 숫자 타입 변수에 문자열을 할당할 수 없으므로 의미상 올바르지 않습니다.

3. 결론

이 글에서는 타입스크립트 컴파일의 전체적인 개요와 컴파일 과정의 각 부분에 대한 관계만 설명해 드렸습니다. 이를 통해 여러분은 타입스크립트 소스 코드를 탐색하며 실제로 어떤 식으로 구현되어있는지 확인하실 수 있을 것입니다.

이 글의 내용을 좀 더 심층적으로 이해하려면 타입스크립트 컴파일러 내부 를 읽으시길 추천해 드립니다. 해당 글에서는 실제 어느 부분의 코드인지, 그리고 어떻게 서로 호출되는지를 자세히 설명하고 있습니다.

🚀 한국어로 된 프런트엔드 아티클을 빠르게 받아보고 싶다면 Korean FE Article(https://kofearticle.substack.com/)을 구독해주세요!

0개의 댓글