런타임과 컴파일타임
프로그래밍 언어는 일반적으로 고수준과 저수준 언어로 구분할 수 있다. 자바스크립트는 대표적인 고수준 언어에 속하며 컴파일러나 인터프리터에 의해 저수준 프로그래밍 언어 즉, 기계가 이해할 수 있는 언어로 번역되어 실행된다.
소스코드의 컴파일이 완료되면 프로그램이 메모리에 적재되어 실행되는데 이 시간을 런타임이라고 한다. 즉, 런타임은 컴파일 과정을 마친 응용 프로그램이 사용자에 의해 실행되는 과정이다.
자바스크립트 런타임
대표적인 자바스크립트 런타임으로 크롬이나 사파리 같은 인터넷 브라우저와 Node.js 등이 있다. 자바스크립트 런타임은 다양한 구성 요소로 이루어져 있는데 주요 구성 요소로 자바스크립트 엔진, 웹 API, 콜백 큐, 이벤트 루프, 렌더 큐가 있다.
💡 자바스크립트는 대표적인 인터프리터 언어로 별도의 컴파일 과정이 존재하지 않는다고 알려져 있다. 하지만 엄밀히 말해 자바스크립트에도 컴파일 단계가 존재한다. 자바스크립트를 해석하고 실행하는 역할을 하는 V8 엔진은 때때로 자바스크립트 코드를 최적화하기 위해 컴파일 단계를 거친다. 이 과정은 실행 속도를 향상하기 위한 목적으로 자바스크립트 코드를 캐싱하여 이후 실행 시간을 단축한다.
트랜스파일
이라고 부르기도 한다. 다음은 타입스크립트 컴파일러가 소스코드를 컴파일하여 프로그램이 실행되기까지의 과정이다.💡 AST
컴파일러가 소스 코드를 해석하는 과정에서 생성된 데이터 구조다. 컴파일러는 어휘적 분석과 구문 분석을 통해 소스 코드를 노드 단위의 트리 구조로 구성한다.
코드 검사기로서의 타입스크립트 컴파일러
타입스크립트 컴파일러는 코드에 타입 오류가 없는지를 확인한다. 타입스크립트에서는 컴파일타임에 코드 타입을 확인하기 때문에 코드를 실행하지 않고도 오류가 있다는 것을 바로 알 수 있다.
코드 변환기로서의 타입스크립트 컴파일러
타입스크립트 컴파일러는 타입을 검사한 다음에 타입스크립트 코드를 각자의 런타임 환경에서 동작할 수 있도록 구버전의 자바스크립트로 트랜스파일한다.
이 때 타입스크립트 컴파일러는 타입 검사를 수행한 후 코드 변환을 시작하는데, 이때 타입 오류가 있더라도 일단 컴파일을 진행한다. 타입스크립트 코드가 자바스크립트 코드로 변환되는 과정은 타입 검사와 독립적으로 동작하기 때문이다. 타입스크립트 코드의 타이핑이 잘못되어 발생하는 에러는 자바스크립트 실행과정에서 런타임 에러로 처리된다.
타입스크립트 컴파일 이후에는 타입이 제거되어 순수한 자바스크립트 코드만 남는다. 컴파일된 코드가 실행되고 있는 런타임에서는 타입 검사를 할 수 없기 때문에 주의해야 하는 경우도 있다.
interface Square {
width: number;
}
interface Rectangle extends Square {
height: number;
}
type Shape = Square | Rectangle;
function calculateArea(shape: Shape) {
if(shape instanceof Rectanfle) {
//'Rectangle' only refers to a type, but is being used as a value here
// Property 'height' does not exist on type 'Shape'
// Property 'height' does not exist on type 'Square'
return shape.width * shape.height;
}else{
return shape.width * shape.width;
}
instanceof 체크는 런타임에 실행되지만 Rectangle은 타입이기 때문에 자바스크립트 런타임은 해당 코드를 이해하지 못한다.
tsc와 바벨은 소스코드를 ES5 이하의 자바스크립트 코드로 컴파일해주는다는 점에서 동일하다. 하지만 tsx와 달리 바벨은 타입 검사를 하지 않는다. 최신 버전의 자바스크립트 코드를 낮은 버전으로 컴파일하는 것이 바벨의 주된 역할이다.
💡 바벨
ECMAScript 2015 이후의 코드를 현재 또는 오래된 브라우저와 호환되는 버전으로 변환해주는 자바스크립트 컴파일러이다.
컴파일러는 하나의 프로그램으로 이를 구현한 소스 파일이 존재한다. 타입스크립트 공식 깃허브에서 compiler라는 별도의 폴더로 구성된 타입스크립트 컴파일러를 찾아볼 수 있다. 타입스크립트 컴파일러의 구체적인 동작을 살펴보자
프로그램
타입스크립트 컴파일러는 tsc 명령어로 실행된다. 컴파일러는 tsconfig.json에 명시된 컴파일 옵션을 기반으로 컴파일을 수행한다. 먼저 전체적인 컴파일 과정을 관리하는 프로그램 객체가 생성된다. 이 프로그램 객체는 컴파일할 타입스크립트 소스 파일과 소스 파일 내에서 임포트된 파일을 불러오는데, 가장 최초로 불러온 파일을 기준으로 컴파일 과정이 시작된다.
스캐너
스캐너는 타입스크립트 소스 파일을 어휘적으로 분석하여 토큰을 생성하는 역할을 한다.
const woowa = "bros";
위의 변수를 선언하는 코드는 스캐너에 의해 다음과 같이 분석된다.
const woowa = "bros";
// const - constKeyword
// ' ' - WhitespaceTrivia
// woowa - Identifier
// ' ' - WhitespaceTrivia
// = - EqualsToken
// ' ' - WhitespaceTrivia
//'bros' - StringLiteral
// ; - SemicolonToken
파서
스캐너가 소스 파일을 토큰으로 나눠주면 파서는 그 토큰 정보를 이용하여 AST를 생성한다. AST는 컴파일러가 동작하는 데 핵심 기반이 되는 자료 구조로, 소스코드의 구조를 트리 형태로 표현한다.
파서는 생성된 토큰 목록을 활용하여 구문적 분석을 수행한다. (예시는 책 확인)
바인더
바인더의 주요 역할은 체커단계에서 타입 검사를 할 수 있도록 기반을 마련하는 것이다. 바인더는 타입 검사를 위해 심볼이라는 데이터 구조를 생성한다. 심볼은 이전 단계의 AST에서 선언된 타입의 노드 정보를 저장한다.
체커와 이미터
체커는 파서가 생성한 AST와 바인더가 생성한 심볼을 활용하여 타입 검사를 수행한다. 이 단계에서 체커의 소스 크기는 이전 단계 파서의 소스 크기보다 매우 크다.
체커의 주요 역할은 AST의 노드를 탐색하면서 심볼 정보를 불러와 주어진 소스 파일에 대해 타입 검사를 진행하는 것이다.
이미터는 타입스크립트 소스 파일을 변환하는 역할을 한다. 즉, 타입스크립트 소스를 자바스크립트(js) 파일과 타입 선언 파일(d.ts)로 생성한다.
이미터는 타입스크립트 소스 파일을 변환하는 과정에서 개발자가 설정한 타입스크립트 설정 파일을 읽어오고, 체커를 통해 코드에 대한 타입 검증 정보를 가져온다.