[TypeScript] 자바스크립트의 런타임과 타입스크립트의 컴파일

2025. 3. 16. 16:39·Development/TypeScript
반응형

 자바스크립트의 유연함이 때로는 장점이 되기도 하지만, 종종 예상치 못한 런타임 오류의 원인이 되기도 합니다. 타입스크립트는 이러한 자바스크립트의 한계를 극복하기 위해 만들어졌습니다. 

 

정적 타입 시스템을 통해 코드 실행 전 많은 오류를 미리 잡고, 더 안전하고 유지보수하기 쉬운 코드를 작성할 수 있게 해줍니다. 

 

하지만 브라우저와 Node.js는 여전히 자바스크립트만 이해할 수 있는데, 타입스크립트 코드는 어떻게 동작하는 걸까요?

 

타입스크립트 컴파일러의 내부 동작 원리를 살펴보고, 소스 코드가 어떻게 검사되고 변환되는지, 컴파일 과정의 각 단계를 살펴보며, 타입스크립트의 안전성과 한계도 정리해보았습니다. 

 

1. 자바스크립트의 런타임과 타입스크립트의 컴파일

// 🎯 타입스크립트에서는 컴파일 시점에 타입 검사가 이루어집니다
function add(a: number, b: number) {
  return a + b;
}

// ✅ 올바른 타입 사용
add(10, 20);     // 결과: 30

// ❌ 컴파일 오류 발생
// add(10, "20");  // 에러: Argument of type 'string' is not assignable to parameter of type 'number'

// 🧩 런타임 오류 예방 사례 1: undefined 속성 접근
function accessProperty(obj: { name?: string }) {
  // 타입스크립트가 없다면 런타임에 오류 발생 가능
  // 타입스크립트는 컴파일 시점에 이런 오류를 잡아냅니다
  return obj.name?.toUpperCase();  // 옵셔널 체이닝으로 안전하게 접근
}

// 🧩 런타임 오류 예방 사례 2: 배열 접근
function getFirstItem(arr: string[] | null) {
  // 타입스크립트가 강제하는 null 체크
  if (arr === null) {
    return "배열이 없습니다";
  }
  
  return arr.length > 0 ? arr[0] : "빈 배열입니다";
}

// 🧩 런타임 오류 예방 사례 3: 스코프 문제
function testScope() {
  const localVar = "이 변수는 함수 내부에서만 접근 가능";
  
  // 블록 스코프를 벗어난 변수 사용 시 컴파일 오류
  // return innerVar; // 에러: Cannot find name 'innerVar'
  
  if (true) {
    const innerVar = "블록 스코프 변수";
    return innerVar; // 이건 정상 작동
  }
  
  return localVar;
}

// 💯 타입스크립트는 오류를 미리 발견하여 안정적인 코드 작성을 도와줍니다

 

1) 런타임과 컴파일타임

(1) 고수준 언어와 저수준 언어

프로그래밍 언어는 크게 두 가지로 구분할 수 있습니다:

  • 고수준 언어 👨‍💻: 사람이 이해하기 쉬운 형식 (예: JavaScript, TypeScript, Python)
  • 저수준 언어 🤖: 컴퓨터가 이해하기 쉬운 형식 (예: 어셈블리어, 기계어)

고수준 언어로 작성된 코드는 컴퓨터가 실행하기 위해 저수준 언어로 변환되는 과정이 필요합니다.

 

(2) 컴파일타임 vs 런타임

컴파일타임(Compile Time) ⚙️

  • 소스코드가 기계어 또는 중간 코드로 변환되는 시점
  • 코드의 문법, 타입 검사 등이 이루어짐
  • 오류가 발견되면 프로그램이 실행되기 전에 알려줌

런타임(Runtime) 🏃‍♂️

  • 프로그램이 실제로 실행되는 시점
  • 사용자 입력 처리, 메모리 할당, 연산 등이 발생
  • 예상치 못한 상황(null 참조, 잘못된 입력 등)으로 오류 발생 가능

 

2) 자바스크립트 런타임

자바스크립트 런타임은 자바스크립트 코드가 실행되는 환경입니다.

대표적인 예 )

  • 웹 브라우저 (Chrome, Firefox, Safari 등)
  • Node.js

주요 구성 요소

  • 자바스크립트 엔진: 코드를 해석하고 실행 (예: V8, SpiderMonkey)
  • 웹 API: DOM, AJAX, setTimeout 등의 기능 제공
  • 이벤트 루프, 콜백 큐: 비동기 작업을 관리

런타임 오류의 예시

// 예시 1: undefined 속성 접근
let foo;
foo.bar; // TypeError: Cannot read properties of undefined (reading 'bar')

// 예시 2: null 객체 속성 접근
const testArr = null;
if (testArr.length === 0) { 
  console.log("zero length");
} // TypeError: Cannot read properties of null (reading 'length')

// 예시 3: 스코프 문제
function testFn() { 
  const foo = "bar";
} 
console.log(foo); // ReferenceError: foo is not defined

💡 참고: 자바스크립트는 흔히 인터프리터 언어로 알려져 있지만, 실제로는 V8 같은 현대 엔진이 내부적으로 JIT(Just-In-Time) 컴파일하여 성능을 최적화합니다.

 

 

3) 타입스크립트의 컴파일

타입스크립트는 소스코드를 자바스크립트로 변환합니다. 이 과정은 기존의 컴파일과 약간 다릅니다:

  • 📝 고수준 언어(TypeScript) → 또 다른 고수준 언어(JavaScript)로 변환
  • 🔄 이러한 변환을 트랜스파일(Transpile) 또는 소스 대 소스 컴파일이라고도 함
  • 🔧 tsc라는 컴파일러를 사용

 

타입스크립트의 가장 큰 장점: 컴파일 타임 타입 체크

function add(a: number, b: number) { 
  return a + b;
}

add(10, 20);       // ✅ 정상 동작
add(10, "20");     // ❌ 컴파일 오류 발생: 'string' 타입은 'number' 타입에 할당할 수 없습니다.

 

런타임 오류를 방지하는 타입스크립트

타입스크립트는 앞서 본 자바스크립트 런타임 오류를 컴파일 시점에 미리 잡아낼 수 있습니다:

// undefined 속성 접근 방지
let foo: { bar?: string };
foo.bar;  // ❌ 오류: 'foo'가 초기화되기 전에 사용되었습니다.

// null 체크 강제
const testArr: string[] | null = null;
if (testArr?.length === 0) {  // 옵셔널 체이닝으로 안전하게 접근
  console.log("zero length");
}

// 스코프 문제 감지
function testFn() { 
  const foo = "bar";
} 
console.log(foo);  // ❌ 오류: 'foo'가 선언되기 전에 사용되었습니다.

 

🌟 타입스크립트를 사용해야 하는 이유

  1. 안전성 🛡️: 컴파일 타임에 많은 오류를 잡아내어 런타임 오류 감소
  2. 가독성 📖: 코드의 의도를 명확히 표현할 수 있는 타입 시스템
  3. 생산성 ⚡: 자동 완성, 리팩토링 지원 등 개발 도구의 향상된 지원
  4. 유지보수성 🔧: 타입 정보를 통해 코드 변경 시 영향 범위 파악 용이

 

2. 타입스크립트 컴파일러

타입스크립트는 자바스크립트의 슈퍼셋(확장)으로, 개발 단계에서 강력한 타입 체크를 제공하지만 결국 브라우저나 Node.js에서 실행되기 위해서는 자바스크립트로 변환되어야 합니다. 이 과정을 담당하는 것이 바로 타입스크립트 컴파일러(tsc) 입니다.

 

타입스크립트 컴파일러가 소스코드를 컴파일하고, 프로그램 실행되기까지의 과정은 아래와 같습니다. 

 

1  tsc 명령어를 실행하여 프로그램 객체가 컴파일 과정을 시작한다.
2  스캐너는 소스 파일을 토큰 단위로 분리한다.
3  파서는 토큰을 이용하여 AST를 생성한다.
4  바인더는 AST의 각 노드에 대응하는 심볼을 생성한다. 심볼은 선언된 타입의 노드 정보를 담고 있다.
5  체커는 AST를 탐색하면서 심볼 정보를 활용하여 타입 검사를 수행한다.
6  타입 검사 결과 에러가 없다면 이미터를 사용해서 자바스크립트 소스 파일로 변환한다

 

1) 코드 검사기로서의 타입스크립트 컴파일러

타입스크립트 컴파일러의 첫 번째 중요한 역할은 코드 검사입니다. 이는 코드를 실행하기 전에 타입 오류를 찾아내는 것을 의미합니다.

예시: 런타임 에러를 컴파일 타임에 잡아내기

자바스크립트에서는

const developer = {
  work() {
    console.log("working...");
  },
};

developer.work(); // 정상 작동: "working..." 출력
developer.sleep(); // 🔴 런타임 에러: TypeError: developer.sleep is not a function

 

타입스크립트에서는

const developer = {
  work() {
    console.log("working...");
  },
};

developer.work(); // ✅ 정상 작동
developer.sleep(); // ❌ 컴파일 에러: Property 'sleep' does not exist on type '{ work(): void; }'

 

💡 핵심 이점: 타입스크립트는 코드를 실행하기 전에 오류를 발견하여 런타임 에러를 미리 방지합니다.

 

 

타입스크립트 컴파일러는 tsc binder를 사용하여 타입 검사를 수행하며, 이 과정에서 타입스크립트 AST(Abstract Syntax Tree)를 분석합니다. 타입 검사 후에는 이 AST를 자바스크립트 코드로 변환합니다.

 

2) 코드 변환기로서의 타입스크립트 컴파일러

타입스크립트 컴파일러의 두 번째 주요 역할은 코드 변환입니다. 타입스크립트 코드는 브라우저나 Node.js와 같은 자바스크립트 런타임에서 직접 실행될 수 없습니다. 따라서 컴파일러는 타입스크립트 코드를 표준 자바스크립트로 변환(트랜스파일)합니다.

 

예시: 타입스크립트에서 자바스크립트로의 변환

타입스크립트 

type Fruit = "banana" | "watermelon" | "orange" | "apple" | "kiwi" | "mango";
const fruitBox: Fruit[] = ["banana", "apple", "mango"];

const welcome = (name: string) => {
  console.log(`hi! ${name} :)`);
};

 

컴파일 후 자바스크립트 코드(ES5 기준)

"use strict";
var fruitBox = ["banana", "apple", "mango"];
var welcome = function (name) {
  console.log("hi! ".concat(name, " :)"));
};

🔎 주목할 점: 컴파일 후에는 모든 타입 정보(type Fruit, : Fruit[], : string)가 제거되었습니다.

 

컴파일러의 target 옵션을 통해 어떤 버전의 자바스크립트로 변환할지 지정할 수 있습니다(예: ES5, ES6, ES2020 등).

 

중요한 특성: 타입 에러가 있어도 컴파일은 진행됨

타입스크립트 컴파일러는 타입 오류가 있더라도 코드 변환을 계속 진행합니다.

이는 타입 검사와 코드 변환이 독립적으로 동작하기 때문입니다.

const name: string = "zig";
const age: number = "zig"; // ❌ 타입 에러: Type 'string' is not assignable to type 'number'

 

위 코드는 타입 에러가 있지만, 다음과 같이 자바스크립트로 컴파일됩니다:

const name = "zig";
const age = "zig"; // 타입 정보가 제거되어 에러가 없어짐

⚠️ 주의: 타입 에러가 있는 코드도 컴파일이 가능하지만, 실제 런타임에서 예상치 못한 동작을 할 수 있습니다.

 

 

3) 런타임에서의 제약: 타입 정보 손실

타입스크립트의 타입 시스템은 컴파일 타임에만 존재합니다.

 

코드가 자바스크립트로 컴파일되면 모든 타입 정보가 사라집니다. 이는 런타임에서 타입 정보를 사용할 수 없다는 것을 의미합니다.

예시: 런타임에서 타입 사용 시 문제

interface Square {
  width: number;
}

interface Rectangle extends Square {
  height: number;
}

type Shape = Square | Rectangle;

function calculateArea(shape: Shape) {
  // ❌ 오류: 'Rectangle'은 타입이므로 런타임에서 값으로 사용할 수 없음
  if (shape instanceof Rectangle) {
    return shape.width * shape.height;
  } else {
    return shape.width * shape.width;
  }
}

🛠️ 해결책: 대신 '속성 체크'나 '타입 가드'를 사용해야 합니다. 타입스크립트의 인터페이스, 타입 별칭, 제네릭 등은 모두 컴파일 타임에만 존재하는 개념이므로, 런타임에서는 이를 직접 참조할 수 없습니다. 대신 자바스크립트의 기본 연산자와 패턴을 활용해 비슷한 기능을 구현합니다.

function calculateArea(shape: Shape) {
  // ✅ 'height' 속성의 존재 여부로 타입 구분
  if ('height' in shape) {
    // 타입스크립트는 이 블록 내에서 shape를 Rectangle로 인식
    return shape.width * shape.height;
  } else {
    return shape.width * shape.width;
  }
}

 

 

TSC vs Babel 차이

더보기

타입스크립트 컴파일러(tsc)와 Babel은 모두 최신 코드를 이전 버전의 자바스크립트로 변환한다는 점에서 유사하지만, 아래 표와 같이 다른 점이 있습니다. 

 

특징  타입스크립트  컴파일러(tsc) Babel
타입 검사 ✅ 수행 ❌ 수행하지 않음
코드 변환 ✅ 수행 ✅ 수행
주요 목적 타입 검사 + 코드 변환 최신 자바스크립트를 구 버전으로 변환

💡 참고: 실제 프로젝트에서는 Babel은 코드 변환을, TypeScript는 타입 검사만을 담당하도록 설정하는 경우도 있습니다.

 

🌟 타입스크립트 컴파일러의 두 가지 핵심 역할

아래 두 역할을 통해 타입스크립트는 개발 단계에서의 안전성을 제공하면서도, 모든 자바스크립트 런타임 환경과의 호환성을 유지할 수 있습니다.

  1. 타입 검사: 코드의 타입 오류를 컴파일 타임에 발견
  2. 코드 변환: 타입스크립트 코드를 자바스크립트로 트랜스파일

 

💡 개발 Tip

  • tsconfig.json의 noEmitOnError 옵션을 true로 설정하면, 타입 에러가 있을 때 자바스크립트 파일을 생성하지 않도록 할 수 있습니다.
  • 개발 단계에서는 --watch 플래그를 사용하여 파일 변경 시 자동으로 컴파일되도록 설정하면 효율적입니다.
  • 런타임에 타입 검사가 필요한 경우, 별도의 런타임 타입 검사 라이브러리(예: io-ts, zod)를 고려해볼 수 있습니다. 

 

 

3. 타입스크립트 컴파일러의 구조와 동작 원리

1) 타입스크립트 컴파일러의 구조

타입스크립트는 개발자가 작성한 타입스크립트 코드를 브라우저나 Node.js가 이해할 수 있는 자바스크립트로 변환해야 합니다. 이 변환 과정을 담당하는 것이 타입스크립트 컴파일러입니다. 컴파일러는 단순히 코드를 변환하는 것뿐만 아니라 타입 검사, 문법 오류 확인 등 다양한 작업을 수행합니다.

 

2) 컴파일러의 다섯 단계

타입스크립트 컴파일러는 소스코드를 자바스크립트로 변환하기 위해 다음 다섯 가지 주요 단계를 거칩니다:

단

1. 스캐너(Scanner) 소스코드를 토큰으로 분리 토큰(Token)
2. 파서(Parser) 토큰을 기반으로 구문 분석 AST(추상 구문 트리)
3. 바인더(Binder) 타입 정보를 AST에 연결 심볼(Symbol)
4. 체커(Checker) 타입 검사 수행 진단 정보(오류)
5. 이미터(Emitter) 최종 JavaScript 코드 생성 .js 파일

 

각 단계를 자세히 살펴보겠습니다.

 

[ 1단계 ] 프로그램(Program)

컴파일 과정은 tsc 명령어가 실행되면서 시작됩니다. 이 시점에서 프로그램 객체가 생성되며, 다음 작업을 수행합니다:

  • tsconfig.json 파일에서 컴파일 옵션 로드
  • 컴파일할 소스 파일 및 관련 파일 식별
  • 전체 컴파일 과정 조정
# 🖥️ 터미널에서 컴파일 실행
tsc index.ts  # 특정 파일 컴파일
# 또는
tsc           # tsconfig.json 설정에 따라 컴파일

 

[ 2 단계 ] 스캐너(Scanner)

스캐너는 타입스크립트 소스코드를 토큰(Token)이라는 최소 의미 단위로 분해합니다. 이 과정을 어휘 분석(Lexical Analysis)이라고 합니다.

 

예를 들어 다음 코드를

const woowa = "bros";

스캐너는 다음과 같은 토큰으로 분해합니다:

  • ConstKeyword (const)
  • WhitespaceTrivia (공백)
  • Identifier (woowa)
  • WhitespaceTrivia (공백)
  • EqualsToken (=)
  • WhitespaceTrivia (공백)
  • StringLiteral ("bros")
  • SemicolonToken (;)

💡 참고: 타입스크립트의 모든 토큰 유형은 SyntaxKind 변수에서 확인할 수 있습니다.

 

[ 3단계 ] 파서(Parser)

파서는 스캐너가 생성한 토큰을 받아 추상 구문 트리(AST: Abstract Syntax Tree)를 생성합니다.

 

AST는 소스코드의 구조를 트리 형태로 표현한 자료구조입니다.

  • 스캐너가 '무엇이 있는지'를 파악했다면, 파서는 '어떤 의미인지'를 파악합니다
  • 예를 들어, 괄호(())가 함수 호출인지, 그룹화 연산자인지 결정합니다
// 📌 예제 코드
function normalFunction() { 
  console.log("normalFunction");
}
normalFunction();

 

 

이 코드의 AST는 대략 다음과 같은 구조를 가집니다:

FunctionDeclaration
├── Identifier (normalFunction)
├── Empty Parameters
└── Block
    └── ExpressionStatement
        └── CallExpression (console.log)
            ├── PropertyAccessExpression
            │   ├── Identifier (console)
            │   └── Identifier (log)
            └── StringLiteral ("normalFunction")

ExpressionStatement
└── CallExpression (normalFunction)

🔍 도구 팁: TypeScript AST Viewer를 사용하면 코드의 AST 구조를 시각적으로 확인할 수 있습니다.

 

[ 4단계 ] 바인더(Binder)

바인더의 주요 역할은 파서가 생성한 AST 노드들을 의미적으로 연결하고, 타입 검사를 위한 심볼(Symbol)을 생성하는 것입니다.

  • 심볼은 코드에서 선언된 식별자(변수, 함수, 클래스 등)에 대한 정보를 담고 있습니다
  • 각 심볼은 다음과 같은 정보를 포함합니다:
    • flags: 심볼의 종류(변수, 함수, 클래스 등)
    • escapedName: 심볼의 이름
    • declarations: 해당 심볼과 관련된 선언 노드 목록
// 📌 심볼 인터페이스 일부 (타입스크립트 소스 코드에서)
export interface Symbol {
  flags: SymbolFlags;       // 심볼 종류 식별자
  escapedName: string;      // 심볼 이름
  declarations?: Declaration[]; // 관련 선언 노드
  // 이하 생략...
}

 

심볼 플래그는 AST에서 선언된 타입의 노드 정보를 저장하는 식별자입니다.

// 📌 심볼 플래그 예시 (일부)
export const enum SymbolFlags {
  None = 0,
  FunctionScopedVariable = 1 << 0,  // var 변수 또는 매개변수
  BlockScopedVariable = 1 << 1,     // let이나 const 변수
  Property = 1 << 2,                // 속성 또는 enum 멤버
  Function = 1 << 4,                // 함수
  Class = 1 << 5,                   // 클래스
  Interface = 1 << 6,               // 인터페이스
  // 이하 생략...
}

 

[ 5단계 ] 체커(Checker)와 이미터(Emitter)

체커(Checker)

체커는 앞서 생성된 AST와 심볼을 바탕으로 타입 검사를 수행합니다.

  • 타입스크립트 컴파일러에서 가장 큰 부분을 차지합니다(약 2.7MB)
  • AST 노드를 순회하면서 각 노드에 대한 타입을 분석하고 검증합니다
  • 타입 오류가 발견되면 진단 정보(에러 메시지)를 생성합니다
// 📌 타입 오류 예시
let name: string = "Woowa";
name = 42;  // ❌ 오류: Type 'number' is not assignable to type 'string'

 

이미터(Emitter)

이미터는 최종적으로 타입스크립트 코드를 자바스크립트 코드로 변환합니다.

  • 자바스크립트 파일(.js)과 타입 선언 파일(.d.ts)을 생성합니다
  • 이미터는 체커의 타입 검사 결과를 활용합니다
  • 타입 정보는 제거되고 실행 가능한 자바스크립트 코드만 남습니다
// 📌 타입스크립트 원본 코드
function greet(name: string): string {
  return `Hello, ${name}!`;
}

// 📌 이미터가 생성한 자바스크립트 코드
function greet(name) {
  return "Hello, " + name + "!";
}

 

컴파일 과정 요약

타입스크립트 컴파일러의 전체 과정을 요약하면 다음과 같습니다:

  1. 프로그램 객체 생성: tsc 명령어 실행으로 컴파일러 시작
  2. 스캐너: 소스코드를 토큰으로 분리 (어휘 분석)
  3. 파서: 토큰을 기반으로 AST 생성 (구문 분석)
  4. 바인더: AST 노드와 심볼 연결, 타입 정보 준비
  5. 체커: AST와 심볼을 기반으로 타입 검사 수행
  6. 이미터: 타입 정보가 제거된 자바스크립트 코드 생성

 

💡개발 Tip

  • 타입 오류 이해하기: 컴파일러 에러 메시지의 원인은 주로 체커 단계에서 발생합니다. 에러 메시지를 이해하면 문제 해결이 쉬워집니다.
  • 컴파일 성능 최적화: 대규모 프로젝트에서는 증분 컴파일을 활용하여 성능을 향상시킬 수 있습니다.
  • AST 탐색 도구 활용: TypeScript AST Viewer를 사용하면 코드의 구조를 더 잘 이해할 수 있습니다.
  • 타입스크립트 컴파일러 API: 타입스크립트는 컴파일러 API를 제공하여 자체 도구를 개발할 수 있습니다.

 

 

반응형
저작자표시 비영리 변경금지 (새창열림)
'Development/TypeScript' 카테고리의 다른 글
  • [TypeScript ] 비동기 호출 - API 에러 핸들링
  • [TypeScript] 타입 활용하기
  • [Typescript] 타입 확장/좁히기
  • [TypeScript] TypeScript와 React Hook (2) - useRef, custom Hook (커스텀 훅)
DREYA
DREYA
한발 한발 나아가는 Delight(기쁨) 개발일지는 이곳에서 → https://velog.io/@jaidy/posts
  • DREYA
    Delight
    DREYA
    • 분류 전체보기
      • Algorithm
      • Psychology
      • Development
        • HTML
        • CSS
        • JavaScript
        • TypeScript
        • React
        • Vue.js
        • Spring
        • SQL
        • 웹 최적화
      • Design
  • 블로그 메뉴

    • 개발일지🍀
  • 공지사항

  • 최근 글

  • hELLO· Designed By정상우.v4.10.1
DREYA
[TypeScript] 자바스크립트의 런타임과 타입스크립트의 컴파일
상단으로

티스토리툴바