
프로그램을 만들기 위해 개발자는 소스코드를 작성합니다.
소스코드는 컴파일러 (Compiler) 에 의해 기계어코드로 변환되어 실행이 가능한 프로그램이 되는데,
이 단계를 컴파일 타임(Compile Time) 이라고 부릅니다.
즉, 컴파일 타임 은
소스코드가 컴파일 과정을 거쳐서
컴퓨터가 인식할 수 있는 기계어 (바이트 코드)로
변환되어 실행할 수 있는 프로그램이 되는 과정을 의미합니다.
컴파일(compile)은 추상화 단계가 다른 고수준에서 저수준 언어로 변환되는 과정을 가리키는데요.
하지만 타입스크립트는 고수준 언어가 저수준 언어로 변환되는 것이 아니라,
고수준 언어 (타입스크립트) => 고수준 언어(자바스크립트) 로 변환되는 것이기 때문에,
트랜스파일(transpile) 이라고 부르기도 합니다.
< 좁은 의미의 컴파일과 트랜스파일을 통틀어서 컴파일 이라고 부르기도 합니다 ! >
먼저,
타입스크립트 컴파일러가 소스코드를 컴파일 해서
프로그램이 실행되기까지의 과정을 정리해 보자면요.
1. 타입스크립트 소스코드를 해석해서 타입스크립트 AST (추상구문트리) 로 만든다.
2. 타입검사기가 AST 를 확인해서 타입을 검사한다.
3. 타입스크립트 AST를 자바스크립트 소스로 변환한다.
4. 자바스크립트코드 소스코드를 자바스크립트 AST 로 만든다. (런타임 시점)
5. AST가 바이트 코드로 변환된다. (런타임 시점)
6. 런타임에서 바이트코드가 평가돼서 프로그램이 실행된다. (런타임 시점)
AST (Abstract Syntax Tree)
컴파일러가 소스코드를 해석하는 과정에서 생성된 자료 구조인데요.
컴파일러는 어휘적분석(lexical analysis) 과 구문분석 (syntax analysis) 을 통해서
소스코드를 노드 단위의 트리구조로 구성합니다.
# 타입스크립트의 소스코드 타입은 1단계와 2단계에서만 쓰입니다.
개발자가 작성한 타입정보는 단지 타입을 확인하는데에만 쓰인다는거죠!
타입스크립트는 컴파일 타임에 타입을 검사하기 때문에 에러가 발생하면
프로그램이 실행되지 않는데요.
이러한 특징 때문에, 타입스크립트를 정적 타입 검사기 (static type checker) 라고도 부릅니다.
아래 예시처럼 컴파일 타임에 타입을 검사해서 런타임에서의 에러를 방지할 수 있습니다.
그리고 타입스크립트 컴파일러의 역할을 설명드리겠습니다.
1. 검사하는 역할의 타입스크립트 컴파일러
타입스크립트 컴파일러는 컴파일 타임에 문법 에러와 타입 관련 에러를 모두 검출합니다.
eating이라는 메서드가 없다는 사실을 컴파일타임에 알려주고 있는 걸 볼 수 있는데요.
이처럼 타입스크립트에서는 컴파일타임에 발견해서
실행 과정에서 발생할 수 있는 문제를 방지해줍니다.
2. 코드를 변환시켜주는 역할의 타입스크립트 컴파일러
타입스크립트 컴파일러는 타입을 검사한 후,
타입스크립트 코드를 각자의 런타임 환경에서 동작할 수 있도록
세팅된 버전의 자바스크립트로 트랜스파일링 해줍니다.
타입스크립트 소스코드는 브라우저와 같은 런타임 환경에서는 실행 될 수 없습니다.
타입스크립트 소스코드를 파싱하고 자바스크립트 코드로 변환하고나서
그제서야 실행할 수 있게 되는거죠.
타입스크립트 컴파일러의 target 옵션을 사용해서
특정 버전의 자바스크립트 소스코드로 컴파일 할 수 있는데요.
ES6 로 설정해서 예시를 보여드리겠습니다.
자바스크립트 파일에서 타입정보가 제거된게 보이실 겁니다.
이제서야 브라우저가 코드를 비로소 이해할 수 있게 되는것이죠.
Q. 타이핑이 잘못되면 컴파일도 진행이 안되는걸까?
이때 타입 오류가 있더라도 일단 컴파일을 진행합니다.
타입스크립트 코드가 자바스크립트 코드로 변환되는 과정은
타입검사와 독립적으로 동작하기 때문이죠.
타입스크립트 코드의 타이핑이 잘못돼서 발생되는 에러는
자바스크립트 실행 과정에서 런타임 에러로 처리됩니다.
하지만 컴파일 된 코드가 실행되고 있는 런타임에서는 타입검사를 할 수 없기 때문에, 주의해야하는 경우도 있습니다.
다음 예시를 보시죠.
instanceof 체크는 런타임에 실행되지만 Oreeyo는 타입이기 때문에
자바스크립트 런타임은 해당 코드를 이해하지 못하죠.
컴파일 과정에서 인터페이스, 타입, 타입 구문이 제거되기 때문에 런타임에서는 타입을 사용할 수 없습니다.
자, 그러면 타입스크립트 컴파일러의 역할을 크게 2가지로 나눌 수 있겠네요!
- 타입스크립트 코드를 구버전의 자바스크립트 코드로 트랜스파일링 한다.
- 코드의 타입 오류를 검사한다.
아 참고로 바벨 이라는 녀석도 있습니다.
바벨 ( Babel )
es5 이후의 코드를 현재나 오래된 브라우저와 호환되는 버전으로
변환해주는 자바스크립트 컵파일러 입니다.
tsc와 바벨은 소스크드를 es5 이하의 자바스크립트 코드로 컴파일해준다는 점에서는 동일합니다!
하지만 tsc는 타입검사를 해주지만,
바벨은 타입검사를 하지 않는다는 차이점이 있겠네요!
타입스크립트 컴파일러의 구조
타입스크립트 컴파일러의 구성요소를 훑어보면서 동작방식을 이해해봅시다.
타입스크립트 컴파일러는 다섯 단계를 거쳐서 타입검사와 자바스크립트 소스 변환을 진행합니다.
그리고 컴파일러는 tsconfig.json에 명시된 컴파일 옵션 세팅을 기반으로 컴파일을 수행하죠.
전체적인 컴파일 과정을 관리하는 프로그램 객체가 생성됩니다.
그 후에 컴파일할 타입스크립트 소스 파일과 소스 파일 내에 import 된 파일들을 불러오는데,
제일 먼저 불러온 파일을 기준으로 컴파일 과정이 시작됩니다.
아래 이미지는 TSC 컴파일러의 실행 과정입니다.
첫번째 단계로 Lexer 는 스캐너(Scanner) 라고 불리기도 합니다.
스캐너는 타입스크립트 소스파일을 어휘적으로 분석 (lexical analysis) 해서 토큰을 생성하는 역할을 합니다.
즉, 소스코드를 작은 단위로 나눠서 토큰으로 변환시키는거죠.
아래 링크에서 스캐너 플러그인을 추가할 수 있습니다.
https://www.npmjs.com/package/playground-ts-scanner
playground-ts-scanner
See the results of the TypeScript Scanner on some code. Latest version: 1.1.0, last published: 3 years ago. Start using playground-ts-scanner in your project by running `npm i playground-ts-scanner`. There are no other projects in the npm registry using pl
www.npmjs.com
두번째 단계로 파서 (Parser) 가 있습니다.
스캐너가 소스파일을 토큰으로 나눠주면 파서는 그 토큰정보를 이용해서 AST 를 생성합니다.
AST의 최상위 노드는 타입스크립트 소스파일이고,
최하위 노드는 파일의 끝 지점으로 구성됩니다.
파서는 이렇게 생성된 토큰 목록을 활용해서 구문적 분석 (Syntax analysis) 을 합니다.
코드의 실질적인 구조를 노드 단위의 트리형태 (AST)로 표현하는거죠.
각각의 노드는 코드 위치, 코드내용 등의 정보를 담고있습니다.
해당하는 토큰이 있을 때,
파서가 AST를 생성하는 과정에서 이 토큰이 실질적으로 함수의 호출인지, 인자인지
또는 그룹 연산자 인지가 결정됩니다.
코드는 다음과 같은 구조로 AST를 구성합니다.
세번째 단계로 바인더 (Binder) 가 있습니다.
바인더의 주요 역할은 Checker 체커 단계에서 타입 검사를 할 수 있도록 준비하는 것 입니다.
바인더는 타입 검사를 위해서 심볼 (Symbol) 이라는 데이터 구조를 생성하는데요.
심볼은 이전 단계의 AST에서 선언된 타입의 노드 정보를 저장하고
심볼과 그에 해당하는 AST 노드를 연결시켜줍니다.
심볼의 인터페이스를 예시를 들어서 보여드리겠습니다.
결과적으로 바인더는 심볼을 생성 하고 해당 심볼과 그에 해당하는 AST 노드를 연결시켜주는 역할이네요!
아래 링크는 심볼을 확인할 수 있는 플러그인 입니다. 설치해서 확인해 보시죠.
https://github.com/orta/playground-ts-symbols
GitHub - orta/playground-ts-symbols
Contribute to orta/playground-ts-symbols development by creating an account on GitHub.
github.com
네번째 단계와 다섯번째 단계로 체커 (Checker) 와 이미터 (Emitter) 가 있습니다.
전체 컴파일 과정에서 타입검사가 차지하는 비중이 제일 큽니다.
체커 (Checker) 의 소스크기가 파서 (Parser) 의 소스크기 보다 엄청 커요.
체커 (Checker) 의 주요 역할은
파서(Parser)가 생성한 AST 노드를 탐색하고
바인더가 생성한 심볼 정보를 참고해서,
해당 소스 파일에 대해서 타입검사를 진행하는 것입니다.
checker.ts 의 getDiagnostics() 함수를 사용해서
타입 검증 및 타입 에러에 대한 에러메시지를 저장합니다.
이미터 (Emitter) 의 주요역할은
타입스크립트 소스 파일을 변환하는 것 입니다.
타입스크립트 소스를 자바스크립트 파일과 타입 선언 파일 (d.ts) 로 생성하는거죠.
이미터는 타입스크립트 소스 파일을 변환하는 과정에서
tsconfig.json 파일을 읽어오고,
체커 ( Checker )를 통해서
코드에 대한 타입 검증 정보를 가져옵니다.
그리고 emitter.ts 소스 파일 내부에 존재하는
emitFiles() 함수를 사용해서 타입스크립트 소스 변환을 진행합니다.
컴파일 과정을 최종 정리 해보자면.
1. tsc 명령어를 실행해서 프로그램 객체가 컴파일 과정을 시작.
2. 스캐너 (Scanner) 가 소스 파일을 토큰 단위로 분리.
3. 파서 (Parser) 가 토큰을 이용해서 AST 를 생성.
4. 바인더 (Binder) 가 AST의 각 노드에 대응하는 심볼 ( Symbol ) 을 생성.
5. 체커 (Checker) 는 AST를 탐색하고, 심볼 정보를 활용해서 타입 검사를 실행.
6. 타입 검증 정보를 가져와서, 이미터 (Emitter)를 사용해서 자바스크립트 파일로 변환.
'개발 > TypeScript' 카테고리의 다른 글
Any VS Unknown VS Never (0) | 2024.03.25 |
---|---|
enum 보다는 union type (0) | 2024.02.27 |
개발 블로그
포스팅이 좋았다면 "좋아요❤️" 누르기 !