🐕 Typescript는 브라우저에서 실행될 수 없다.
처음 웹에 입문하고 절 가장 힘들게 했던 것은 이것이었습니다. 리액트가 중요하다는데..? 타입스크립트가 좋다는데?
쌩으로 CSS 쓰는거보다 SCSS 배워야한다는데? 하면서 여러가지를 설치하고나면
항상 그것을 컴파일 or 트랜스파일해주고 번들링해줄 추가적인 툴들이 필요합니다.
많이 사용되는 것으로는 바벨, 웹팩, 롤업 등이 있을 수 있겠네요.
왜 열심히 만들어둔 우리의 프레임워크, 라이브러리를 그대로 사용할 수 없고
HTML, CSS , JS로 변환하는 과정이 필요한 것일까요?
그것은 브라우저가 인식할 수 있는 파일이 HTML, CSS , JS이기 때문입니다.
즉 리액트로 아무리 JSX를 멋지게 써내려가도 ts로 멋지게 코드를 작성해도
그것이 우리의 웹사이트에서 굴러가게 될 때에는 모두 JS로 돌아가있다는 것이죠!
그 과정을 직접 하는게 아닌 바벨, 타입스크립트 등의 트랜스파일러에게 도움을 받아서 하다보니
마치 마법같다는 느낌을 받기도하고 여러가지 의문점들이 생기기도 합니다.
타입스크립트는 어떻게 자바스크립트로 변환되는 것일까요?
타입스크립트는 같은 로직의 자바스크립트보다 코드의 양이 많을 수 밖에 없는데
타입과 인터페이스를 정의하는데 사용한 많은 양의 코드들이 성능에 영향을 미치지 않을까요?
그 모든 답은 타입스크립트 컴파일러에 있습니다.
👻타입스크립트의 컴파일러
npx tsc hello.ts
tsc 명령어를 통해 우리는 타입스크립트 파일을 동일한 이름의 자바스크립트 파일로 컴파일 할 수 있습니다.
https://developer.mozilla.org/ko/docs/Glossary/Compile
MDN에서는 컴파일을 다음과 같이 표현하고 있네요!
Typescript Deep Dive 페이지에서 TypeScript 컴파일러의 소스가 어디에 위치하는지를 알려줍니다.
https://github.com/Microsoft/TypeScript/tree/main/src/compiler
TypeScript/tree/main/src/compiler 경로에 컴파일을 담당하는 코드들이 존재하는거네요
들어가보면 알 수 있지만 굉장히 많은 파일과 코드들이 존재하고 있습니다.
그 중 key가 되는 역할을 하는 파트들을 구분하면 다음과같이 5개 정도로 나눌 수 있습니다.
Scanner |
Parser |
Binder |
Checker |
Emitter |
이제 실제로 타입스크립트 컴파일러가 동작하는 것을 그려보겠습니다.
🥶동작 흐름
1. tsconfig를 읽는다.(scan한다.)
2. preprocess 파일의 root 부터 시작해서 imports로 연결된 모든 파일을 가능한대로 찾는다.
3. tokenize & parse .ts로 작성된 파일에는 타입스크립트 코드가 있을 것을 기대할 수 있다. 따라서 .ts로 작성된 파일을 추상구문트리(AST)로 변환한다.
4. 추상구문트리로 파싱한 코드를 기준으로 해당 트리에 있는 symbol을 identifier로 변경합니다. (binder)
5. binder와 추상구문트리를 기준으로 타입을 체크합니다.
6. 추상구문트리를 1번에서 읽었던 tsconfig의 옵션에 맞게 변경합니다.
7. emit 추상구문트리를 .js , .d.ts 파일등으로 변경합니다.
거창해보이지만 결국 하는 일을 보면 당연한 유기적 흐름을 따르고 있습니다.
코드를 읽고 -> 읽은 코드를 변환하고 -> 변환한 코드를 이용해 타입을 체크하고 -> 파일로 만든다.
전체적인 흐름은 꽤 당연한 흐름으로 이어지네요
🌞AST를 만들기 위해 필요한 scanner와 parser
앞서 TS 컴파일러의 핵심적인 key가 되는 아이들을 확인했습니다.
이 중 scanner와 parser는 AST를 만드는 데에 사용됩니다.
ys ast viewer 사이트에서 추상구문트리로 변환된 타입스크립트 코드를 직접 확인해볼 수 있습니다.
전 간단하게 hi라는 이름을 가진 객체를 생성해봤어요
먼저 scanner는 우리의 타입스크립트 코드를 토큰화하는 작업을 수행합니다.
여기서 토큰화란 더이상 쪼갤 수 없을때까지 쪼개는 행위라고 간단하게 생각하도록하겠습니다.
위 코드는 다음과 같이 인식할 수 있습니다.
interface Hi {
get:string
}
// interfaceDeclaration
Hi // identifier
get:string // PropertySignature
get // PropertySignature/Identifier
string // PropertySignature/StringKeyword
이렇게 토큰화시킬 수 있을 것입니다!
실제로 사이트에 들어가서 soruceFile부분을 하나하나 클릭해보시면서 확인하면
쉽게 이해하실 수 있을거에요
이렇게 scanner는 ts파일을 읽어내려가면서 코드들을 토큰화시킵니다.
또한 이 과정에서 스캐너는 코드 문자열의 정합성 검사도 진행합니다.
이제 scanner를 통해 토큰화를 완료했습니다. 그렇다면 다음은 parser가 일을 합니다.
parser는 스캐너가 읽어들인 token을 기준으로 트리를 만들어요
바로 이부분이 추상구문트리입니다.
내용을 잘 살펴보면 scanner가 만든 토큰을 기준으로 ast가 만들어진것을 확인할 수 있습니다.
이를 좀 더 세부적인 그림으로 만들어보면 다음과 같은 흐름이 됩니다.
스캐너가 소스코드를 받아서 토큰스트림으로 만든다음
파서는 그 토큰스트림을 기반으로 AST를 뱉는 구조네요!
😋타입 체킹을 위한 사전작업을 하는 binder
바인더는 AST를 읽어서 타입 검사에 필요한 데이터를 수집하는 과정입니다.
이 과정을 통해서 메타데이터를 수집하고, 타입 분석에 필요한 계층 구조와 같은 것들을 만듭니다.
바인더는 스코프별로 어떤 identifier가 있는지 확인합니다.
제가 느끼기에 이부분은 자바스크립트가 코드를 평가하는 과정과 유사해보이는 것 같아요
🤢타입을 체킹하는 checker
chcker는 실제로 타입을 체크하는 일을 수행하는 파일입니다.
우리의 컴파일러는 이제 드디어 타입체킹을 위한 사전작업을 마쳤습니다.
코드를 읽으면서 토큰화 시키고 토큰을 이용해 추상구문트리를 생성하고
추상구문트리를 이용해 타입체킹에 필요한 데이터를 수집하는것까지 마쳤어요
그럼 이제 타입을 체킹해봐야겠죠?
타입스크립트는 신택스트리를 순차적으로 순회하면서 체크 가능한 모든 것들을 체크하는 방식으로 validation을 진행합니다. 그런데 타입스크립트는 구조적 타이핑(structural typing)을 기반으로 하고 있습니다.
따라서 외적인 구조를 비교하는 것부터 시작하여 구조의 안으로 파고들어가게됩니다.
예컨대 interface Hi { str : string }와 {str: 100}을 비교한다고 생각해보면
둘의 외적인 구조는 object의 형태로 동일합니다. 따라서 이 시점에서 둘의 타입은 같습니다.
하지만 타입스크립트는 이제 안으로 파고들어가면서 구조가 완전히 동일한지를 판단하게될것입니다.
🤮파일을 생성해주는 emitter
이제 checker까지 거쳐가면서 타입체킹까지 완료했다면 자바스크립트 파일을 만들어야합니다.
emitter의 역할은 신택스 트리를 읽어서 파일을 리턴하는 것이라고 정의할 수 있는데
emitter가 하는 일은 크게 다음과 같이 표현할 수 있습니다.
.js , .map . d.ts 파일을 만들기 |
추상구문트리를 text로 변환하기 |
루프 내부의 _i, _i2와 같은 임시변수를 추적하기 |
추상구문트리를 추상구문트리로 변환하기 |
추상구문트리를 추상구문트리로 변환한다는 것이 대체 무슨소리인가 싶지만
타입스크립트 추상구문트리를 자바스크립트 추상구문트리로 변환한다고 생각하면 이해가 쉽습니다.
타입스크립트를 컴파일하는 과정에서 생성한 추상구문트리와
자바스크립트의 추상구문트리는 생김새가 조금 다르거든요!
🐶마치며
내부적으로는 굉장히 복잡하고 많은 과정이 존재하지만 커다란 흐름만 얕게 흝어보았습니다.
이를 통해 알 수 있는 것은 타입스크립트 컴파일러가 하는 일이 굉장히 많다..라는 것이었습니다.
그러다보니 swc를 사용하자는 이야기가 나오는 것 같아요
next.js 개발 환경을 만들 때 swc를 사용할것인지 물어보는 탭이 있는데
이렇게 많은 일을 수행하는데에는 자바스크립트로 작성된 툴보다
좀 더 빠르게 일하는 언어가 더 좋을수도 있겠다는 생각이 듭니다.
swc는 rust로 작성되어 기존의 babel과 같은 트랜스파일러에 비해 훨씬 속도가 빠르다고해요!
swc도 많이 흥미로우니 한번 공부해봐야겠네요
추가로 이렇게 타입스크립트 파일은 런타임에 평가되는것이 아니라
정적 컴파일을 빌드단계에서 진행한 다음 브라우저에서 구동되기 때문에 성능에 영향을 미치지 않는답니다!
🐶레퍼런스
https://yceffort.kr/2022/05/how-typescript-compiler-works
https://velog.io/@sehyunny/how-ts-compiler-compiles
https://github.com/microsoft/TypeScript/wiki/Performance
https://yceffort.kr/2021/03/performance-tip-of-typescript
https://medium.com/jspoint/typescript-compilation-the-typescript-compiler-4cb15f7244bc
https://www.geeksforgeeks.org/how-typescript-compilation-works/
https://www.tutorialspoint.com/how-typescript-compilation-works
https://basarat.gitbook.io/typescript/overview
'typescript' 카테고리의 다른 글
왜 타입스크립트는 DOM 요소를 확신하지 못할까? (0) | 2023.06.02 |
---|---|
useState가 반환하는 setState의 타입은 어떻게 설정할까? (0) | 2023.05.14 |
TypeScript interface cheat sheet (0) | 2023.04.19 |
reduxtoolkit 을 typescript환경에서 사용하기 (1) | 2023.04.19 |
ts.config.json 설정 알아보기 (0) | 2023.04.18 |