코드 변환 입문: OXC Transformer로 TypeScript 컴파일 구현하기
OXC Transformer를 사용하여 TypeScript를 JavaScript로 컴파일하는 방법을 배워보세요. JSX 변환과 구문 다운그레이드를 지원하며, Babel보다 더 빠르고 간단합니다.
코드 변환 입문: OXC Transformer로 TypeScript 컴파일 구현하기
프론트엔드 개발에서 우리는 종종 코드를 “변환”해야 합니다. TypeScript를 JavaScript로, ES6 문법을 ES5로, JSX를 일반 JavaScript 함수 호출로 변환하는 것입니다.
오늘은 Babel보다 더 빠른 코드 변환 도구인 OXC Transformer를 배워보겠습니다.
코드 변환이란 무엇인가요?
왜 코드를 변환해야 하나요?
브라우저는 우리가 작성한 모든 코드를 직접 실행할 수 없습니다:
| 소스 코드 | 문제 | 해결책 |
|---|---|---|
| TypeScript | 브라우저가 타입 구문을 인식하지 못함 | 타입 제거, JS로 변환 |
| ES6+ 문법 | 구형 브라우저 미지원 | ES5로 다운그레이드 |
| JSX | 브라우저가 파싱할 수 없음 | React.createElement로 변환 |
일반적인 변환 시나리오
1. TypeScript → JavaScript
// 입력: TypeScript
interface User {
name: string;
age: number;
}
function greet(user: User): string {
return `Hello, ${user.name}!`;
}
// 출력: JavaScript
function greet(user) {
return `Hello, ${user.name}!`;
}
2. JSX → JavaScript
// 입력: JSX
function Button({ onClick, children }) {
return <button onClick={onClick}>{children}</button>;
}
// 출력: JavaScript
function Button({ onClick, children }) {
return React.createElement("button", { onClick }, children);
}
3. ES6+ → ES5
// 입력: ES6+
const greet = (name) => `Hello, ${name}!`;
class Person { ... }
// 출력: ES5
var greet = function(name) { return "Hello, " + name + "!"; };
function Person() { ... }
Transformer는 무엇을 할 수 있나요?
OXC Transformer는 다음 기능을 제공합니다:
- TypeScript 타입 제거: TS를 순수 JS로 변환
- JSX 변환: JSX 문법을 함수 호출로 변환
- 구문 다운그레이드: 새 문법을 구문법으로 변환 (일부 지원)
- 소스맵 보존: 디버깅을 위한 Source Map 생성
빠른 시작
설치
npm install @oxc-transform
기본 사용
import { transformSync } from "@oxc-transform";
const code = `
interface User {
name: string;
}
function greet(user: User) {
return \`Hello, \${user.name}!\`;
}
`;
const result = transformSync(code, {
lang: "ts",
});
console.log(result.code);
출력:
function greet(user) {
return `Hello, ${user.name}!`;
}
비동기 변환
import { transform } from "@oxc-transform";
const result = await transform(code, {
lang: "ts",
});
console.log(result.code);
실전: TypeScript를 JavaScript로 컴파일하기
TypeScript 컴파일 과정을 완전히 연습해 봅시다.
1단계: TypeScript 프로젝트 생성
mkdir ts-project
cd ts-project
npm init -y
npm install @oxc-transform
mkdir src
src/index.ts 생성:
// src/index.ts
interface Config {
apiUrl: string;
timeout: number;
debug?: boolean;
}
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
class ApiClient {
private config: Config;
constructor(config: Config) {
this.config = config;
}
async request<T>(method: HttpMethod, path: string): Promise<T> {
const url = `${this.config.apiUrl}${path}`;
const response = await fetch(url, {
method,
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
}
}
// 사용 예시
const client = new ApiClient({
apiUrl: "https://api.example.com",
timeout: 5000,
});
export { ApiClient, Config, HttpMethod };
2단계: 컴파일 스크립트 생성
build.js 생성:
// build.js
import { transformSync } from "@oxc-transform";
import fs from "fs";
import path from "path";
function compileTypeScript(inputPath, outputPath) {
// 소스 파일 읽기
const code = fs.readFileSync(inputPath, "utf-8");
// 변환
const result = transformSync(code, {
lang: "ts",
// JSX 보존 (있는 경우)
jsx: "react",
// 대상 ES 버전
target: "es2020",
// Source Map 생성
sourcemap: true,
});
// 출력 디렉토리 확인
const outputDir = path.dirname(outputPath);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// 출력 파일 작성
fs.writeFileSync(outputPath, result.code);
// Source Map 작성
if (result.map) {
fs.writeFileSync(`${outputPath}.map`, result.map);
}
console.log(`✓ Compiled: ${inputPath} → ${outputPath}`);
}
// 컴파일
compileTypeScript("./src/index.ts", "./dist/index.js");
3단계: 컴파일 실행
node build.js
출력:
✓ Compiled: ./src/index.ts → ./dist/index.js
생성된 dist/index.js 확인:
class ApiClient {
#config;
constructor(config) {
this.#config = config;
}
async request(method, path) {
const url = `${this.#config.apiUrl}${path}`;
const response = await fetch(url, {
method,
headers: {
"Content-Type": "application/json"
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
}
}
const client = new ApiClient({
apiUrl: "https://api.example.com",
timeout: 5e3
});
export { ApiClient };
다음을 확인할 수 있습니다:
- TypeScript 타입 정의가 제거됨
private필드가#프라이빗 필드 문법으로 변경됨- 코드 구조는 유지됨
설정 옵션 상세
lang - 언어 타입
transformSync(code, {
lang: "js", // JavaScript
lang: "jsx", // JavaScript + JSX
lang: "ts", // TypeScript
lang: "tsx", // TypeScript + JSX
});
target - 대상 ES 버전
transformSync(code, {
// 지원하는 대상 버전
target: "es5",
target: "es2015",
target: "es2016",
target: "es2017",
target: "es2018",
target: "es2019",
target: "es2020",
target: "es2021",
target: "es2022",
target: "esnext",
});
jsx - JSX 변환 모드
// React 클래식 모드
transformSync(code, {
jsx: "react", // React.createElement(...)
});
// React 자동 런타임
transformSync(code, {
jsx: "automatic",
jsxImportSource: "react", // 자동 import
});
// 커스텀 JSX 팩토리
transformSync(code, {
jsx: "react",
jsxFactory: "h", // React.createElement 대신 h(...)
jsxFragment: "Fragment",
});
sourcemap - 소스맵
transformSync(code, {
sourcemap: true, // Source Map 생성
sourcemap: "inline", // 인라인 Source Map
});
JSX 변환 상세
OXC Transformer는 JSX 변환을 완전히 지원합니다.
React 클래식 모드
// 입력
function App() {
return (
<div className="container">
<h1>Hello, World!</h1>
<button onClick={() => alert("clicked")}>Click me</button>
</div>
);
}
// 출력 (클래식 모드)
function App() {
return React.createElement(
"div",
{ className: "container" },
React.createElement("h1", null, "Hello, World!"),
React.createElement("button", { onClick: () => alert("clicked") }, "Click me")
);
}
React 자동 런타임
// 설정
transformSync(code, {
lang: "jsx",
jsx: "automatic",
jsxImportSource: "react",
});
// 출력 (자동 런타임)
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
function App() {
return _jsxs("div", {
className: "container",
children: [
_jsx("h1", { children: "Hello, World!" }),
_jsx("button", { onClick: () => alert("clicked"), children: "Click me" })
]
});
}
Vue JSX
transformSync(code, {
lang: "tsx",
jsx: "react",
jsxFactory: "h",
jsxFragment: "Fragment",
});
실전 미니 프로젝트: 일괄 컴파일
더 실용적인 일괄 컴파일 도구를 만들어 봅시다.
일괄 컴파일 스크립트 생성
// batch-compile.js
import { transformSync } from "@oxc-transform";
import fs from "fs";
import path from "path";
const config = {
inputDir: "./src",
outputDir: "./dist",
extensions: [".ts", ".tsx"],
target: "es2020",
};
function compileFile(inputPath, outputPath) {
const code = fs.readFileSync(inputPath, "utf-8");
const lang = inputPath.endsWith(".tsx") ? "tsx" : "ts";
const result = transformSync(code, {
lang,
target: config.target,
jsx: "automatic",
jsxImportSource: "react",
sourcemap: true,
});
// 출력 디렉토리 확인
const outputDir = path.dirname(outputPath);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// 파일 확장자 변경 .ts -> .js
const jsOutputPath = outputPath.replace(/\.tsx?$/, ".js");
fs.writeFileSync(jsOutputPath, result.code);
if (result.map) {
fs.writeFileSync(`${jsOutputPath}.map`, result.map);
}
console.log(`✓ ${inputPath} → ${jsOutputPath}`);
}
function walkDir(dir, callback) {
const files = fs.readdirSync(dir);
for (const file of files) {
const filePath = path.join(dir, file);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
walkDir(filePath, callback);
} else if (config.extensions.some((ext) => file.endsWith(ext))) {
callback(filePath);
}
}
}
function build() {
console.log("🚀 Starting build...\n");
// 출력 디렉토리 정리
if (fs.existsSync(config.outputDir)) {
fs.rmSync(config.outputDir, { recursive: true });
}
fs.mkdirSync(config.outputDir, { recursive: true });
// 모든 파일 컴파일
walkDir(config.inputDir, (inputPath) => {
const relativePath = path.relative(config.inputDir, inputPath);
const outputPath = path.join(config.outputDir, relativePath);
compileFile(inputPath, outputPath);
});
console.log("\n✨ Build complete!");
}
build();
package.json에 추가
{
"scripts": {
"build": "node batch-compile.js",
"dev": "node batch-compile.js && node dist/index.js"
}
}
Babel과의 비교
기능 비교
| 기능 | Babel | OXC Transformer |
|---|---|---|
| TypeScript 컴파일 | ✅ | ✅ |
| JSX 변환 | ✅ | ✅ |
| 구문 다운그레이드 | ✅ 완전 | 🚧 일부 지원 |
| 플러그인 시스템 | ✅ 풍부 | ❌ 아직 없음 |
| Polyfill 주입 | ✅ | ❌ |
| 커스텀 변환 | ✅ | ❌ |
성능 비교
// benchmark.js
import { transformSync as oxcTransform } from "@oxc-transform";
import * as babel from "@babel/core";
const code = fs.readFileSync("./large-file.ts", "utf-8");
// OXC
console.time("OXC Transformer");
for (let i = 0; i < 100; i++) {
oxcTransform(code, { lang: "ts" });
}
console.timeEnd("OXC Transformer");
// Babel
console.time("Babel");
for (let i = 0; i < 100; i++) {
babel.transformSync(code, {
presets: ["@babel/preset-typescript"],
});
}
console.timeEnd("Babel");
결과:
OXC Transformer: 150ms
Babel: 2800ms
OXC Transformer가 Babel보다 약 18배 빠릅니다!
마이그레이션 제안
| 상황 | 제안 |
|---|---|
| 순수 TypeScript 컴파일 | OXC로 충분 |
| 간단한 JSX 프로젝트 | OXC로 충족 가능 |
| ES5로 구문 다운그레이드 필요 | 일단 Babel 사용 |
| 커스텀 플러그인 필요 | Babel 계속 사용 |
| 대형 프로젝트 점진적 마이그레이션 | 혼합 사용 가능 |
혼합 사용 방안
{
"scripts": {
"build": "oxc-transform ./src --out-dir ./dist-oxc && babel ./src --out-dir ./dist-babel --presets @babel/preset-typescript"
}
}
자주 묻는 질문
데코레이터는 어떻게 처리하나요?
OXC는 현재 데코레이터 지원이 제한적입니다. 프로젝트에서 데코레이터를 사용한다면 일단 Babel이나 TypeScript 컴파일러를 사용하는 것을 권장합니다.
생성된 코드 형식이 안 예뻐요?
OXC Transformer는 변환에 집중하며, 포맷팅은 담당하지 않습니다. Oxfmt와 함께 사용할 수 있습니다:
# 먼저 변환, 그 다음 포맷팅
oxc-transform ./src --out-dir ./dist
oxfmt "./dist/**/*.js" --write
Source Map이 작동하지 않아요?
변환 시 Source Map을 활성화하고 경로가 올바른지 확인하세요:
transformSync(code, {
sourcemap: true,
filename: "source.ts", // 소스 파일명 지정
});
번들 도구와 통합
Vite 통합
OXC는 공식 Vite 플러그인을 제공합니다:
// vite.config.js
import { defineConfig } from "vite";
import oxc from "vite-plugin-oxc";
export default defineConfig({
plugins: [oxc()],
});
Rollup 통합
// rollup.config.js
import oxc from "rollup-plugin-oxc";
export default {
input: "src/index.ts",
output: {
dir: "dist",
format: "esm",
},
plugins: [oxc()],
};
요약
본문에서는 OXC Transformer의 핵심 사용법을 소개했습니다:
| 내용 | 설명 |
|---|---|
| TypeScript 컴파일 | 타입 제거, JS 생성 |
| JSX 변환 | React.createElement 또는 자동 런타임으로 변환 |
| 설정 옵션 | lang, target, jsx, sourcemap |
| 성능 우위 | Babel보다 18배 이상 빠름 |
Transformer의 핵심 가치: Rust의 속도로 코드 변환 완료.
다음 단계
Transformer의 더 많은 설정 옵션을 알고 싶으신가요? **OXC 한국어 문서 - Transformer 챕터**를 방문하세요.
다음 글에서는 OXC Minifier를 배워보겠습니다. 프로덕션 코드를 더 작고 빠르게 만들어 봅시다!
💡 관련 읽기: