Babel 및 SWC를 이용한 JavaScript 코드 변환 메커니즘 이해
Takashi Yamamoto
Infrastructure Engineer · Leapcell

JavaScript의 진화와 코드 변환기의 필요성
지난 10년 동안 JavaScript 개발 환경은 극적인 변화를 겪었습니다. ECMAScript 표준의 빠른 발전과 함께 새롭고 강력하며 표현력이 풍부한 코드 작성 방식을 제공하는 언어 기능들이 끊임없이 도입되고 있습니다. 하지만 이러한 기능들이 다양한 브라우저와 Node.js 버전에서 채택되는 속도는 종종 불일치합니다. 이러한 불일치는 최신 언어 혁신을 활용하면서도 다양한 환경에서 애플리케이션이 안정적으로 실행되도록 보장하려는 개발자에게 어려움을 안겨줍니다. 바로 이때 Babel과 SWC와 같은 JavaScript 코드 변환기가 필수적인 도구로 자리 잡습니다. 이들은 개발자가 오늘날 최신 JavaScript를 작성하고 모든 곳에서 안정적으로 배포할 수 있도록 하는 중요한 다리 역할을 합니다. 이 기사에서는 이러한 강력한 도구들의 내부 작동 방식을 살펴보고, 그 원리를 설명하며, 실제 적용 사례를 보여줄 것입니다.
JavaScript 코드 변환의 분해
Babel과 SWC를 자세히 살펴보기 전에 코드 변환의 기반이 되는 핵심 개념을 이해하는 것이 중요합니다.
- 추상 구문 트리 (Abstract Syntax Tree - AST): 대부분의 코드 변환 도구의 핵심에는 추상 구문 트리가 있습니다. AST는 소스 코드의 구문 구조를 트리 형태로 표현한 것입니다. 트리 내의 각 노드는 변수 선언, 함수 호출 또는 표현식과 같은 코드의 구문 요소를 나타냅니다. AST는 개념적으로 언어에 구애받지 않지만, 각 언어별 구현은 구체적입니다. AST는 원시 텍스트보다 프로그래밍 방식으로 조작하기 쉬워 분석 및 변환의 기초를 형성합니다.
- 파서 (Parser): 파서는 소스 코드를 입력으로 받아 AST로 변환하는 프로그램입니다. 코드의 구문을 분석하여 언어의 문법 규칙을 준수하는지 확인합니다. 구문 오류가 있다면 파서는 일반적으로 이를 보고합니다.
- 순회 (Traversal): AST가 생성된 후, 순회는 트리의 각 노드를 방문하는 프로세스를 의미합니다. 이는 종종 깊이 우선 탐색 또는 너비 우선 탐색 알고리즘을 사용하여 수행됩니다. 순회 중에 방문자(특정 노드 유형에 대해 작동하는 함수)는 노드를 검사하고 수정할 수 있습니다.
- 변환기/플러그인 (Transformer/Plugin): 변환기(Babel의 플러그인 또는 SWC의 변환으로 구현되는 경우가 많음)는 AST를 조작하는 함수 또는 함수 집합입니다. 이러한 조작에는 새 구문을 이전 구문으로 변환(트랜스파일링), 코드 최적화 또는 사용자 지정 로직 삽입이 포함될 수 있습니다.
- 코드 생성기 (Code Generator): AST가 변환된 후, 코드 생성기는 수정된 AST를 받아 실행 가능한 소스 코드로 다시 변환합니다. 이 출력 코드는 일반적으로 대상 환경과 호환되는 JavaScript 버전입니다.
Babel: 미래의 JavaScript 컴파일러
Babel은 아마도 가장 잘 알려져 있고 널리 사용되는 JavaScript 컴파일러일 것입니다. 이는 개발자가 오늘날 차세대 JavaScript를 작성할 수 있도록 하는 매우 모듈화되고 확장 가능한 도구입니다.
원리: Babel은 "파싱-변환-생성" 원칙에 따라 작동합니다.
- 파싱 (Parse): 입력 JavaScript 코드를 AST로 변환하기 위해 파서(종종
@babel/parser
기반, 이전에는babylon
으로 알려짐)를 사용합니다. 이 AST는 널리 인정받는 JavaScript AST 표준인 ESTree 사양을 준수합니다. - 변환 (Transform): Babel은 이 AST를 순회하며 일련의 플러그인을 적용합니다. 각 플러그인은 ES6 화살표 함수를 ES5 함수 표현식으로 변환하거나 JSX 구문을
React.createElement
호출로 변환하는 것과 같은 특정 변환을 담당합니다. 플러그인은 일반적인 사용 사례(예: 최신 JavaScript를 대상 환경으로 컴파일하기 위한@babel/preset-env
)를 위해 종종 "프리셋"으로 번들링됩니다. - 생성 (Generate): 마지막으로
@babel/generator
는 변환된 AST를 받아 디버깅을 용이하게 하는 선택적 소스 맵과 함께 해당 JavaScript 코드를 출력합니다.
예시: 화살표 함수 변환
간단한 예시를 통해 ES6 화살표 함수를 ES5 함수로 변환하는 과정을 살펴보겠습니다.
입력 (ES6):
const add = (a, b) => a + b;
Babel 플러그인 의사 코드 (개념적):
// 플러그인이 어떻게 작동하는지에 대한 단순화된 표현입니다. module.exports = function ({ types: t }) { return { visitor: { ArrowFunctionExpression(path) { const { node } = path; // 화살표 함수에 블록 본문(예: `=> { return x; }`) // 또는 암시적 반환(예: `=> x`)이 있는지 확인합니다. const body = t.isBlockStatement(node.body) ? node.body : t.blockStatement([t.returnStatement(node.body)]); // 화살표 함수를 일반 함수 표현식으로 대체합니다. path.replaceWith( t.functionExpression( null, // 익명 함수 node.params, body, false, // 제너레이터 아님 false // 비동기 함수 아님 ) ); }, }, }; };
출력 (ES5):
var add = function add(a, b) { return a + b; };
Babel의 적용 사례:
- 트랜스파일링: 가장 일반적인 사용 사례는 새로운 ECMAScript 기능(ES6+, JSX, TypeScript)을 이전 버전의 보다 널리 지원되는 JavaScript로 변환하는 것입니다.
- 폴리필링:
@babel/preset-env
는 기본적으로 지원되지 않는 내장 기능(예:Promise
또는Array.prototype.includes
)에 대한 폴리필을 자동으로 주입할 수 있습니다. - 코드 최적화: 특정 Babel 플러그인을 통해 데드 코드 제거, 상수 폴딩 및 기타 최적화를 달성할 수 있습니다.
- 사용자 지정 구문: Babel은 사용자 지정 구문을 지원하도록 확장될 수 있지만, 이는 일반 개발에서는 덜 일반적입니다.
SWC: 매우 빠른 웹 컴파일러
SWC(Speedy Web Compiler)는 JavaScript 도구 체인에 비교적 최근에 등장했지만, 뛰어난 성능으로 인해 빠르게 인기를 얻었습니다. Rust로 작성되었으며, Babel과 유사한 기능을 달성하지만 훨씬 더 빠른 속도를 목표로 합니다.
원리: SWC는 Babel과 동일한 기본 "파싱-변환-생성" 원리를 따르지만, Rust로 구현되어 뚜렷한 이점을 제공합니다.
- 파싱 (Parse): SWC는 Rust로 작성된 자체적으로 고도로 최적화된 파서를 갖습니다. 이 파서는 소스 코드에서 AST를 생성하는 데 매우 빠릅니다.
- 변환 (Transform): SWC의 변환도 Rust로 구현되어 Rust의 동시성 및 메모리 안전 기능을 활용합니다. ESNext 기능, TypeScript 및 JSX를 포함한 광범위한 변환을 지원합니다. 또한 코드 압축 기능도 제공합니다.
- 생성 (Generate): SWC의 코드 생성 단계 또한 동등하게 최적화되어 변환된 AST를 JavaScript 코드로 신속하게 다시 변환합니다.
성능 이점: SWC의 주요 차별점은 속도입니다. Rust와 같은 저수준 언어로 작성되었기 때문에, SWC는 특히 대규모 코드베이스의 경우 JavaScript 기반 도구(예: Babel)보다 훨씬 빠르게 코드를 파싱, 변환 및 생성할 수 있습니다. 이는 컴파일 속도가 개발자 생산성에 직접적인 영향을 미치는 최신 JavaScript 빌드 시스템 및 개발 워크플로우에 특히 매력적입니다.
예시: SWC 구성 (Babel preset-env 와 유사)
기본 구현은 Rust로 되어 있지만, 개발자는 Babel과 유사하게 구성 파일(예: swcrc
)을 통해 SWC와 상호 작용합니다.
// .swcrc { "jsc": { "parser": { "syntax": "ecmascript", "jsx": true, "dynamicImport": true }, "transform": { "react": { "runtime": "automatic", "pragma": "React.createElement", "pragmaFrag": "React.Fragment", "useBuiltins": true }, "constEnum": false, "optimizer": { "simplify": true, "globals": { "typeof": { "window": "object" } } } }, "target": "es2015", // 변환 대상 ECMAScript 버전 "loose": false, "minify": { "compress": { "unused": true }, "mangle": true } }, "module": { "type": "commonjs" // 또는 "es6", "umd" 등 } }
이 구성은 SWC에게 JSX를 사용하여 ECMAScript를 파싱하고, 자동 런타임을 사용하여 React 코드를 변환하고, ES2015를 대상으로 하며, 압축을 적용하도록 지시합니다.
SWC의 적용 사례:
- 빠른 트랜스파일링: 대규모 모노레포, 복잡한 빌드 파이프라인, 컴파일 속도가 중요한 환경(예: Next.js, Vite)에 이상적입니다.
- 번들링 및 압축: SWC는 자체 번들러(
@swc/cli
) 및 압축기를 포함하여 일반적인 빌드 단계를 위한 올인원 솔루션을 제공합니다. - TypeScript 컴파일: SWC는 TypeScript 컴파일을 매우 효율적으로 수행하며, 순수 속도 면에서
tsc
와 자주 비교되거나 능가합니다. - ESM을 CommonJS로 변환: 최신 ES 모듈과 이전 CommonJS 환경 간의 원활한 통합을 용이하게 합니다.
시너지 관계
Babel과 SWC는 유사한 목적을 수행하지만 상호 배타적이지는 않습니다. 많은 프로젝트에서 전략적으로 이 둘을 조합하여 사용합니다. 예를 들어, 프로젝트에서 개발 및 프로덕션 빌드 중 매우 빠른 TypeScript 트랜스파일링 및 압축을 위해 SWC를 사용하는 동시에, SWC가 아직 지원하지 않거나 동일한 방식으로 구현하지 않는 더 특수한 변환이나 실험적인 기능을 위해 특정 Babel 플러그인을 계속 사용할 수 있습니다. 선택은 종종 성능 요구 사항과 플러그인 생태계의 폭, 그리고 특정 프로젝트 요구 사항 간의 균형을 맞추는 데 달려 있습니다.
자신감 있게 최신 JavaScript 활용하기
Babel 및 SWC와 같은 JavaScript 코드 변환기는 최신 웹 개발의 근본적인 기둥입니다. 이들은 개발자가 최신 언어 기능을 수용하고, 생산성을 향상하며, 다양한 플랫폼에서 일관되게 실행되는 강력한 애플리케이션을 구축할 수 있도록 지원합니다. 특히 추상 구문 트리의 역할에 대한 기본 원리를 이해함으로써 개발자는 도구 체인에 대해 정보에 입각한 결정을 내리고 이러한 강력한 컴파일러를 최대한 활용할 수 있습니다. 오늘날 미래 지향적인 코드를 작성하는 능력은 JavaScript 생태계의 독창성과 지속적인 진화의 증거이며, 변환기는 조용하지만 엄청나게 강력한 가능성을 열어주는 역할을 합니다.