사전 트랜스파일
개념

트랜스파일

gabury1고친 사람 github-actions[bot]

트랜스파일은 한 언어로 쓴 소스 코드를 다른 언어의 소스 코드로 바꿔 적는 일입니다. 바꾼 결과도 사람이 열어 읽을 수 있는 코드입니다. TypeScript 로 쓴 코드를 브라우저가 실행하는 자바스크립트로 바꾸는 일이 대표적입니다.

쉽고 빠른 이해

트랜스파일은 코드를 다른 언어의 코드로 옮겨 적습니다. TypeScript 파일을 넣으면 같은 일을 하는 자바스크립트 파일이 나옵니다.

브라우저는 자바스크립트만 돌립니다. 오래된 브라우저는 새 문법을 모릅니다. 개발자가 쓰고 싶은 언어와 브라우저가 받는 언어가 다를 때 트랜스파일이 그 사이를 이어 줍니다.

도는 순서는 셋입니다.

  1. 코드를 읽어 문법 구조를 나무 모양으로 펼칩니다
  2. 나무에서 바꿀 가지를 목표 언어의 표현으로 고칩니다
  3. 고친 나무를 다시 코드 글자로 적습니다

대가도 있습니다. 브라우저가 돌리는 코드는 개발자가 쓴 코드와 모양이 다릅니다. 오류가 난 줄을 원래 코드에서 찾으려면 둘을 짝지어 둔 파일이 따로 있어야 합니다. 빌드 단계도 하나 늘어납니다.

필요 없을 때도 있습니다. 코드를 실행할 브라우저나 서버가 이미 그 언어와 그 버전을 받으면 옮길 이유가 없습니다.

상세

한국어로 쓴 요리법을 영어 요리법으로 옮겨 적는 일을 떠올려 봅니다. 옮긴 뒤에도 요리법은 사람이 읽고 따라 하는 글입니다. 재료와 순서도 바뀌지 않습니다. 받는 사람이 영어만 읽기 때문에 옮길 뿐입니다.

트랜스파일은 소스 코드를 다른 언어의 소스 코드로 옮기는 일입니다. 옮기는 동안 프로그램이 하는 일은 바뀌지 않습니다. 이 일을 하는 프로그램을 트랜스파일러라고 부릅니다.

컴파일과 가르는 기준

컴파일러는 한 언어로 쓴 프로그램을 다른 언어로 옮기는 프로그램입니다. 흔히 떠올리는 컴파일은 C 소스를 기계어로 옮기는 일입니다. 사람이 쓰는 언어를 CPU(Central Processing Unit, 중앙 처리 장치)가 실행하는 낮은 수준의 명령으로 내립니다.

트랜스파일도 옮기는 일이라 컴파일의 한 갈래입니다. 다른 점은 나오는 것의 수준입니다. 아래 표는 두 일에 무엇을 넣고 무엇이 나오는지 나란히 보입니다.

흔한 컴파일 트랜스파일
넣는 것 소스 코드 소스 코드
나오는 것 기계어 다른 언어의 소스 코드
예 C 소스 → 기계어 TypeScript → 자바스크립트

둘이 갈리는 칸은 「나오는 것」 하나입니다. 결과가 또 다른 고수준 언어의 코드라서 사람이 열어 읽을 수 있습니다. 그래서 소스 대 소스 컴파일(source-to-source compilation)이라고도 부릅니다. 트랜스파일러를 그냥 컴파일러라고 부르기도 합니다.

옮기는 이유

트랜스파일이 필요해지는 이유는 둘입니다. 언어가 다른 경우와 같은 언어의 버전이 다른 경우입니다. 둘 다 브라우저에서 가장 잘 드러납니다.

코드를 받아 실행하는 프로그램을 실행 환경이라고 부릅니다. 브라우저도, 서버에서 자바스크립트를 돌리는 Node.js 도 실행 환경입니다. 실행 환경마다 받아 주는 언어와 버전이 정해져 있습니다.

브라우저가 사람이 쓴 코드를 받아 바로 실행하는 언어는 자바스크립트뿐입니다. 개발자가 다른 언어로 쓰고 싶으면 누군가 그 코드를 자바스크립트로 바꿔 줘야 합니다.

TypeScript 가 대표적인 예입니다. TypeScript 는 자바스크립트에 변수와 함수의 타입을 적는 문법을 더한 언어입니다. 브라우저는 이 타입 표기를 읽지 못합니다. 그래서 실행하기 전에 표기를 걷어 낸 자바스크립트로 옮깁니다.

두 번째 이유는 버전 차이입니다. 자바스크립트 문법은 표준인 ECMAScript가 새 버전을 낼 때마다 늘어납니다. 새 문법으로 쓴 코드를 그 문법이 나오기 전의 브라우저에 보내면 문법 오류로 멈춥니다. 트랜스파일러가 새 문법을 같은 일을 하는 옛 문법으로 풀어 적으면 오래된 브라우저에서도 돕니다.

옮기기 전과 뒤의 코드

앞의 두 이유를 코드 한 벌씩으로 봅니다. 옮기기 전과 뒤에 같은 호출을 넣었습니다. 그 결과는 줄 끝 주석으로 붙였습니다.

먼저 TypeScript 입니다. a: number 는 a 가 숫자라는 표기입니다.

TypeScript
function add(a: number, b: number) {
  return a + b;
}
add(1, 2); // 3

트랜스파일러는 타입 표기를 지운 자바스크립트를 내놓습니다.

JavaScript
function add(a, b) {
  return a + b;
}
add(1, 2); // 3

사라진 것은 : number 뿐입니다. 타입 표기는 실행에 쓰이지 않으므로 지워도 결과가 같습니다.

다음은 새 문법을 옛 문법으로 푸는 예입니다. => 로 함수를 짧게 적는 화살표 함수는 ES2015(ECMAScript 2015)에 들어온 문법입니다.

const 도 같은 버전에 들어왔습니다. const 는 값을 다시 넣지 않을 변수를 선언하는 키워드입니다.

JavaScript
const double = (x) => x * 2;
double(4); // 8

그보다 앞선 버전에는 변수를 선언하는 키워드가 var 하나뿐이었습니다. 옛 버전만 아는 브라우저를 위해 트랜스파일러는 이 코드를 function 과 var 로 풀어 적습니다.

JavaScript
var double = function (x) {
  return x * 2;
};
double(4); // 8

코드는 길어졌지만 같은 호출이 같은 값을 냅니다. 트랜스파일은 모양을 바꾸고 동작은 지킵니다.

트랜스파일러 안에서 거치는 단계

트랜스파일러는 글자를 찾아 바꾸지 않습니다. 코드의 구조를 읽고 그 구조를 고친 뒤 다시 적습니다. 이 소절은 그 세 단계를 앞 소절의 add 함수로 봅니다.

첫 단계는 파서가 맡습니다. 파서는 코드 글자를 처음부터 읽으며 문법에 맞춰 조각을 가립니다. 어디부터 어디까지가 함수 하나이고, 그 안의 어느 글자가 매개변수인지를 나눕니다.

파서가 내놓는 결과는 나무 모양의 자료구조입니다. 이 구조를 추상 구문 트리(AST, Abstract Syntax Tree)라고 부릅니다. 함수 하나가 노드 하나가 됩니다. 그 아래에 매개변수와 본문이 매달립니다.

트리로 펼치는 이유는 글자가 아니라 뜻을 보기 위해서입니다. => 라는 글자는 문자열 안에도 나올 수 있습니다. 트리에서는 그것이 화살표 함수인지 그냥 글자인지가 갈려 있습니다. 그래서 바꿀 곳만 골라낼 수 있습니다.

둘째 단계에서 트리를 고칩니다. 화살표 함수 노드는 function 노드로 바꿉니다. 타입 표기 노드는 떼어 냅니다. 아래 그림은 add 함수의 트리를 고치기 전과 고친 뒤로 나란히 보입니다.

flowchart TD
    subgraph 전["고치기 전"]
        F1["함수 선언 add"] --> P1["매개변수 a"]
        F1 --> Q1["매개변수 b"]
        F1 --> B1["본문"]
        P1 --> T1["타입 표기 number"]
        Q1 --> U1["타입 표기 number"]
        B1 --> R1["return a + b"]
    end
    subgraph 뒤["고친 뒤"]
        F2["함수 선언 add"] --> P2["매개변수 a"]
        F2 --> Q2["매개변수 b"]
        F2 --> B2["본문"]
        B2 --> R2["return a + b"]
    end
    전 -->|타입 표기 노드를 떼어 낸다| 뒤

타입 표기 노드 둘만 떨어집니다. 나머지 트리는 바뀌지 않습니다.

셋째 단계에서 고친 트리를 다시 코드 글자로 적어 냅니다. 이 마지막 단계를 코드 생성이라고 부릅니다. 세 단계를 한 줄로 이으면 아래와 같습니다.

flowchart TD
    A["TypeScript 소스 코드"] -->|파서가 읽는다| B["추상 구문 트리"]
    B -->|바꿀 노드를 고친다| C["고친 추상 구문 트리"]
    C -->|코드 생성| D["자바스크립트 소스 코드"]

쓴 코드와 도는 코드가 달라지는 대가

트랜스파일을 쓰면 치르는 것이 셋 있습니다. 오류 위치가 어긋납니다. 빌드 단계가 늘어납니다. 옮길 수 없는 것이 남습니다.

브라우저가 실행하는 것은 옮긴 뒤의 코드입니다. 그래서 오류가 나면 브라우저는 옮긴 코드의 줄 번호를 알려 줍니다. 개발자가 쓴 파일에서는 그 줄에 다른 코드가 있을 수 있습니다.

이 어긋남을 메우는 파일이 소스맵입니다. 소스맵은 옮긴 코드의 각 위치가 원래 파일의 어느 줄 어느 칸이었는지 짝지어 둡니다. 브라우저의 개발자 도구가 이 파일을 읽어 오류를 원래 코드 위치로 보여 줍니다.

flowchart TD
    A["쓴 코드"] -->|트랜스파일| B["옮긴 코드"]
    A -->|트랜스파일| M["소스맵"]
    B -->|브라우저가 실행| E["오류: 옮긴 코드의 줄"]
    E --> T["개발자 도구가 소스맵을 읽는다"]
    M --> T
    T --> O["원래 파일의 줄"]

빌드 단계도 하나 늘어납니다. 파일을 고칠 때마다 옮기는 일이 다시 돌아야 브라우저에 반영됩니다.

이 일은 대개 혼자 돌지 않습니다. 번들러는 여러 자바스크립트 파일을 브라우저로 보낼 몇 개의 파일로 묶는 도구입니다. 트랜스파일은 보통 번들러가 파일을 묶는 과정 안에서 함께 돕니다.

옮길 수 없는 것도 있습니다. 트랜스파일러는 문법만 바꿔 적습니다. 오래된 브라우저에 없는 함수를 새로 만들어 주지는 않습니다.

배열에 값이 들어 있는지 묻는 includes 메서드가 그 예입니다. [1, 2].includes(2) 는 새 문법이 아니라 평범한 함수 호출이라 바꿔 적을 것이 없습니다. 그런데 이 메서드가 생기기 전의 브라우저에서 부르면 그런 함수가 없다는 오류가 납니다.

빠진 함수는 폴리필이 채웁니다. 폴리필은 같은 일을 하는 함수를 코드로 미리 넣어 두는 방식입니다.

트랜스파일이 필요 없는 경우

옮길지 말지는 실행 환경이 두 가지를 받는지로 갈립니다. 언어를 받는지, 그 버전의 문법을 받는지입니다.

flowchart TD
    A{"실행 환경이 이 언어를 받나"} -->|아니오| B["다른 언어로 옮긴다"]
    A -->|예| C{"이 버전의 문법을 받나"}
    C -->|아니오| D["옛 문법으로 풀어 적는다"]
    C -->|예| E["옮기지 않는다"]

실행 환경이 이미 그 언어와 그 버전을 받으면 옮길 이유가 없습니다. Node.js 처럼 서버에서 도는 자바스크립트는 개발자가 실행 환경의 버전을 직접 고릅니다. 그래서 오래된 브라우저를 위해 문법을 낮추는 일은 서버 코드에서는 대개 필요 없습니다.

언어가 다르면 이야기가 다릅니다. TypeScript 처럼 실행 환경이 모르는 언어로 썼다면 서버에서도 옮기는 단계는 남습니다.

셰이더 언어 사이의 트랜스파일

이 이름은 자바스크립트 밖에서도 쓰입니다. 그래픽 카드에서 도는 프로그램이 그 예입니다.

GPU(Graphics Processing Unit, 그래픽 처리 장치)는 그래픽 카드에 든 계산 장치입니다. 화면의 많은 픽셀을 한꺼번에 계산하는 데 씁니다.

셰이더는 이 GPU 에서 도는 작은 프로그램입니다. 화면의 픽셀마다 색을 계산하는 일 따위를 맡습니다.

프로그램이 GPU 에 일을 시킬 때는 그래픽 API(Application Programming Interface, 응용 프로그램 인터페이스)를 거칩니다. 운영체제마다 쓰는 그래픽 API 가 다릅니다.

셰이더를 적는 언어도 그래픽 API 마다 다릅니다. 아래 표가 그 짝 둘입니다.

그래픽 API 쓰는 곳 받는 셰이더 언어
Direct3D 윈도 HLSL(High-Level Shading Language)
Metal 애플 기기 MSL(Metal Shading Language)

한 셰이더를 두 곳에서 돌리려면 두 언어로 따로 준비해야 한다는 뜻입니다.

WebGPU 는 웹 페이지가 GPU 를 쓰게 해 주는 브라우저 기능입니다. 셰이더는 WGSL(WebGPU Shading Language) 한 가지 언어로만 받습니다.

브라우저는 먼저 페이지가 열린 기계가 어느 그래픽 API 를 쓰는지 봅니다. 그리고 WGSL 소스를 그 API 가 받는 셰이더 언어로 옮깁니다.

flowchart TD
    W["WGSL 소스"] --> B["브라우저가 옮긴다"]
    B -->|윈도| H["HLSL"] --> D["Direct3D"]
    B -->|애플 기기| M["MSL"] --> T["Metal"]
    D --> G["GPU"]
    T --> G

앞의 자바스크립트 예와는 옮기는 때가 다릅니다. 자바스크립트는 개발자가 빌드할 때 옮깁니다. WGSL 은 페이지가 도는 중에 브라우저 안에서 옮깁니다.

관련 항목

트랜스파일이 한 갈래로 속하는 코드 번역 방식

컴파일러 · 크로스 컴파일 · JIT 컴파일 · AOT 컴파일 · 인터프리터 · 어셈블러

트랜스파일러 안에서 코드가 거치는 처리 단계

파서 · 어휘 분석 · 구문 분석 · 추상 구문 트리 · 중간 표현 · 코드 생성

트랜스파일을 해 주는 도구

Babel · tsc · esbuild · SWC · 번들러 · webpack · Vite

트랜스파일을 거쳐 자바스크립트가 되는 언어

TypeScript · CoffeeScript · ClojureScript · Elm · ReScript

트랜스파일한 코드가 도는 언어와 실행기

자바스크립트 · ECMAScript · 브라우저 · 자바스크립트 엔진 · Node.js · 런타임

트랜스파일과 함께 호환 문제를 다루는 개념

폴리필 · 소스맵 · 하위 호환 · 브라우저 호환성 · 기능 감지

번들러 안에서 트랜스파일과 함께 도는 코드 변환

트리 셰이킹 · 미니파이 · 코드 분할 · 데드 코드 제거 · 스코프 호이스팅

셰이더 트랜스파일이 오가는 셰이더 언어

셰이더 · WGSL · HLSL · MSL · GLSL · SPIR-V · WebGPU

다른 이름: transpile · transpilation · transpiling · 트랜스파일링 · 트랜스파일러 · transpiler · 소스 대 소스 컴파일 · source-to-source compilation · 트랜스컴파일 · transcompilation