Typescript Compiler

How typescript api compiles ts to js?
- I’ve already wrote a simple typescript compiler blog post before, this is the more deeper version of typescript compiler api.
Reference
Program
|
|
typescript는 program을 생성하고, type check를 한 뒤, emit하는 방식으로 컴파일이 진행됩니다. ms doc에 따르면 아래 helper function을 사용하면 더 쉽게 컴파일이 가능합니다.
|
|
이를 real world로 예를 든다면 아래 와 같이 isolated 환경에서 dynamic하게 custom code를 받아서 transpile 후 실행 시킬 수 있습니다.
|
|
자 이제 그럼 다시 compiler로 들어가보도록 하겠습니다.
앞서 3줄의 코드에서 보였듯, ts program은 3가지 단계에 거쳐 진행됩니다.
- Source Code to Data (syntax tree)
- Type Checking (bind / check)
- Creating Files (emit)
1. Source Code to Data
Source code에서 의미있는 data로 변경하기 위해서는 syntax tree
개념이 존재합니다.
Compiler는 frontend와 backend로 나눠지고, frontend에서 input code를 받아, 의미 있는 내부 형태로 구성하며 이를 기반으로 backend를 통해서 target output으로 변경을 합니다. 이런 구조를 취하는 이유는 adptor pattern 처럼 원하는 target language에 따라서 편하게 새로운 backend 또는 frontend를 갈아 끼우기 위해서 입니다. 또한 이런 유연한 구조를 위해서 내부적으로 AST (abstract syntax tree)를 도입하였습니다.
Syntax tree는 크게 2가지로 나뉩니다.
- Scanner (src/compiler/scanner.ts), aka
Lexer
- Parser (src/compiler/parser.ts)
Scanner은 text를 syntax tokens으로 변경합니다.
Parsesr은 syntax tokens를 tree로 변경합니다.
실제 코드를 보면
- parser가 source code를 받음
- parser가 scanner instance 생성
- parser가 scanner의
textToToken
를 통해 source code의 text 청크들 syntax token으로 변환 - parser가 syntax token의 stream들을 tree 형식으로 구성
parseJsonText
정리하면
- Scanner: 소스코드를 컴파일러가 이해할 수 있는 토큰 시퀀스로 변환합니다.
- Parser: 토큰 스트림을 분석하여 AST를 생성합니다.
- 함수 선언, 변수 선언, 조건문, 반복문 등의 구조를 식별합니다.
2. Type Checking
Checking the syntax tree
Parsing process를 통해 stream of Syntax tree가 생성되면
- Binder, Syntax -> Symbols
- Converts identifiers in syntax tree to symbols
- Type Check, Use binder and syntax tree to look for issues in code.
2.1. Type Checking: Binder
Binder, post-parser grab bag는 아래와 같은 역할을 수행합니다.
- identifiers가 정의되어있는 Symbol Tables 생성
- Sets up ‘parent’ on all syntax tree nodes
- Make flow node for narrowing
- Validate script vs module conformance
1번에서 symbol이란 scope에 따른 identifier를 말합니다.
2번이 무슨말인지 찾아보니, AST는 비록 트리구조이지만, parser에 의해 생성된 AST는 아직 각 노드에 부모 노드에 대한 정보가 없어 상위 context를 파악하기 어렵다고 합니다. Binder는 AST를 방문(매우 무거운 작업)하면서 각 노드에 대한 부모 노드 정보를 설정할 수 있습니다.
3번의 flow nodes는 위에서 설명한 대로 scope를 찾아가면서 if conditional, functional scope 처럼 flow를 파악하는 것을 뜻합니다.
이렇게 flow graph가 만들어지게 되면 나중에 type check시에 typescript는 scope를 narrow하면서 check할 수 있습니다. 이 덕분에 typeof 같은 Type Guard가 typescript 타입 체크에 영향을 줄 수 있습니다.
Binder가 Symbols table를 생성하면 Program.emit()
을 호출하여 Emit worker가 AST가 javascript source code로 변환될 수 있도록 합니다.
Emitter가 running할때 getDiagnostics()
를 호출하며, 이 함수를 통해 Type Checker
(src/compiler/checker.ts)가 실행됩니다.
Emitter는 AST를 traverse(walk)하며 each node에 상응하는 check function를 실행합니다.
2.2. Type Checking: Type Checker
모든 syntax tree node에는 그에 상응하는 check function이 정의되어 있습니다. 이를 통해서 syntax tree에 정의되어있는 node들을 타고 가면서 type check가 가능합니다.
타입스크립트는 source, target로 타입을 구분하는데, const greet: string = “Hello World’의 경우에 string은 source, ‘Hello World’는 target이 되어 각각에 대해 check됩니다.
방대한 양의 type check이다 보니, 이번 post에서는 여기까지만 파악하고 다음으로 넘어가도록 하겠습니다.
3. Creating Files
Syntax tree -> JS
TS에는 여러 transformers가 존재하며, 지정된 버전에 따라서 실행되는 transformers가 달라집니다.
이 과정을 거쳐서 typescript는 *.js, *.map, *.d.t를 만들어냅니다.