5장 트랜스파일과 폴리필
- TC39(Technical Committee 39): ECMA 하위 위원회로, 자바스크립트 표준을 개발하고 유지하며 단계적으로 발전시키고 있음
- 이에 따라 브라우저도 함께 발전해야 함
- 새로운 자바스크립트 기능 사용 시 발생하는 호환성 문제는 트랜스파일과 폴리필을 통해 해결할 수 있음
- 트랜스파일러는 최신 기능이 지원되지 않는 브라우저에서도 안정적으로 동작할 수 있도록 도움
5.1 트랜스파일을 도와주는 도구, 바벨
바벨(Babel)은 최신 자바스크립트 코드를 구형 브라우저와 호환되는 코드로 변환해주는 트랜스파일러
5.1.1 바벨의 필요성
배경
- 2015년 ES6에서 많은 새로운 기능과 문법이 도입되어 개발이 더욱 강력하고 편리해짐
- 하지만 인터넷 익스플로러 11, 사파리 9 같은 구형 브라우저에서는 최신 기능을 지원하지 않음
- 많은 사용자가 최신 브라우저를 사용하지 않거나, 기업 환경에서 특정 버전의 브라우저 호환성을 유지해야 하는 경우 ES6 기능을 사용할 수 없음
트랜스파일러란?
- 한 버전의 언어를 다른 버전으로 변환해주는 도구
- 자바스크립트에서는 최신 문법을 구형 브라우저에서 동작하도록 코드를 변환해준다는 의미
예시
// ES6
const sum = (a, b) => a + b;
// ES5로 트랜스파일
var sum = function sum(a, b) {
return a + b;
};ES6의 모든 명세가 트랜스파일 가능한 것은 아님 (변환이 불가능한 경우도 있음)
바벨의 역사
- 바벨 등장 전에는 Traceur, es6-shim 등이 있었음
- 2014년 세바스찬 멕킨지가 6to5라는 이름으로 처음 개발
- 이후 이름을 Babel로 변경하고, ES5 트랜스파일을 넘어 다양한 자바스크립트 버전과 실험적 기능을 지원하는 다목적 트랜스파일러로 발전
바벨의 특징
- 플러그인 시스템: 유연한 확장 가능
- 광범위한 문법 지원: ES6을 넘어 다양한 자바스크립트 문법 지원
- 프리셋 제공: 여러 플러그인을 쉽게 설정 가능
@babel/preset-env@babel/preset-react@babel/preset-typescript@babel/preset-flow
- 활발한 커뮤니티: 지속적인 업데이트와 지원
지금도 리액트 프로젝트에서 바벨이 필수일까?
| 상황 | 추천 도구 |
|---|---|
| 구형 브라우저 지원 필요, 안정성 중시 | 바벨 (오랜 시간 검증됨) |
| 모던 브라우저만 지원, 빌드 속도/간단한 설정 중시 | Vite, esbuild 등 |
5.1.2 바벨의 동작 방식
바벨은 **추상 구문 트리(AST)**를 기반으로 동작하며, 변환 과정에 필요한 기능들을 독립적인 패키지로 관리
5.1.2.1 추상 구문 트리 (AST)
정의
- 소스코드의 구조를 트리 형태로 표현한 자료구조
- 컴파일러와 인터프리터가 소스코드를 분석하고 변환하는 데 사용하는 핵심 개념
구성요소
- 노드(Node)
- 자식 노드(Child Node)
- 최상위 노드(Root Node)
AST Explorer에서 추상 구문 트리를 쉽게 확인해볼 수 있음
AST를 사용하는 대표적인 예시
| 도구 | AST 활용 방식 |
|---|---|
| ES Module | 파싱 단계에서 코드를 AST로 변환하여 문법 검증 및 import문 분석으로 모듈 의존성 파악 |
| ESLint | AST를 사용해 코드 린팅 수행. 각 노드를 탐색하며 린팅 규칙 적용 후 문제 위치와 메시지를 포함한 보고서 생성 |
| Babel | acorn 파서 기반의 Babylon 파서를 사용하여 실험적 기능까지 지원 (현재 @babel/parser) |
자바스크립트 AST 파서
자바스크립트 코드를 파싱해서 AST 자료구조로 변환하는 도구:
acornesprimaespree@babel/parser
5.1.2.2 바벨이 코드를 변환하는 과정
바벨의 코드 변환은 3단계로 진행됨:
소스코드 → [파싱] → AST → [변환] → 수정된 AST → [출력] → 변환된 코드
| 단계 | 설명 | 사용 패키지 |
|---|---|---|
| 1. 파싱 | 소스코드를 읽어 AST로 변환. 최신 ECMAScript, JSX, Flow, TypeScript 지원 | @babel/parser |
| 2. 변환 | AST를 탐색하며 변환이 필요한 노드를 찾아 변경. 깊이 우선 방식으로 탐색 | @babel/traverse |
| 3. 출력 | 수정된 AST를 다시 코드로 변환하여 최종 출력 | @babel/generator |
변환 단계의 플러그인 예시
@babel/plugin-transform-block-scoping:const/let→var@babel/plugin-transform-arrow-functions: 화살표 함수 → 일반 함수
@babel/core
파싱, 변환, 출력의 세 단계를 모두 포함하는 바벨의 핵심 패키지
- 위에서 설명한
@babel/parser,@babel/traverse,@babel/generator를 의존성으로 포함 - 파싱부터 출력까지 모든 기능을 수행할 수 있음
- 바벨 package.json 참고
transform API 사용 예시
transform API는 파싱 → 플러그인/프리셋 적용 → 코드 변환 → 출력까지 한번에 실행할 수 있는 유용한 API
import { transform } from "@babel/core";
const code = `
const sum = (a, b) => a + b;
`;
const output = transform(code, {
presets: ["@babel/preset-env"],
});
console.log(output.code);패키지 분리의 장점
바벨이 내부 동작을 여러 패키지로 나누어 제공하는 이유:
- 모듈화와 유연성: 필요한 기능만 선택적으로 사용 가능
- 유지보수 용이: 각 패키지를 독립적으로 업데이트 가능
5.1.3 바벨 사용해보기
5.1.3.1 바벨 구성 파일
바벨로 코드를 트랜스파일하려면 먼저 구성 파일이 필요하다.
구성 파일이란?
- 바벨의 변환 작업을 정의하는 모든 옵션이 포함된 파일
transformAPI 사용 시 이 파일을 읽어 코드 변환 방법을 결정
구성 파일 종류
babel.config.*(js, json, cjs, mjs).babelrcpackage.json의babel필드
주요 옵션
1. presets
프리셋을 설정. 배열 형태로 여러 프리셋 지정 가능
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}2. plugins
프리셋에서 제공하지 않는 개별 기능이나 실험적 기능을 추가할 때 사용
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": ["@babel/plugin-transform-runtime", "@babel/plugin-proposal-class-properties"]
}| 플러그인 | 설명 |
|---|---|
@babel/plugin-transform-runtime | 바벨의 런타임 헬퍼를 재사용하여 코드 중복 방지 |
@babel/plugin-proposal-class-properties | ES6 표준에 없는 클래스 속성 문법 지원 |
3. 기타 옵션
| 옵션 | 설명 |
|---|---|
env | 환경별(development, production 등) 설정을 다르게 적용 |
ignore | 특정 파일/디렉터리를 트랜스파일에서 제외 |
only | 트랜스파일할 파일/디렉터리를 명시적으로 지정 |
exclude | 특정 플러그인/프리셋에 대해서만 제외할 파일 지정 |
include | 특정 플러그인/프리셋에 대해서만 포함할 파일 지정 |
sourceMaps | 소스맵 생성 여부 (true, false, "inline" 등) |
compact | 출력 코드 길이를 줄여 파일 크기 축소 (가독성 저하) |
minified | 트랜스파일된 코드를 축소(minify)할지 여부 |
retainLines | 소스코드의 줄 번호를 유지하여 디버깅 용이하게 함 |
extends | 다른 바벨 구성 파일을 확장하여 사용 |
overrides | 특정 파일/디렉터리에 대해 별도의 바벨 구성 적용 |
5.1.3.2 단독으로 사용하기
바벨을 단독으로 사용할 때는 CLI 패키지인 @babel/cli를 사용하면 편리하게 코드를 변환할 수 있다.
npm install --save-dev @babel/cli @babel/core트랜스파일의 한계
트랜스파일은 ES6 명세를 ES5 명세의 조합으로 코드를 대체하는 방식으로 동작한다. 하지만 모든 기능이 변환 가능한 것은 아니다.
| 구분 | 기능 | 변환 방식 |
|---|---|---|
| 변환 가능 | 클래스 | 함수 기반 프로토타입 패턴 |
| 화살표 함수 | 일반 함수 표현식 | |
| 템플릿 리터럴 | 문자열 연결 | |
| 구조 분해 할당 | 일반 변수 할당 | |
| 스프레드 연산자 | Object.assign 또는 배열 메서드 | |
| 옵셔널 체이닝 | 삼항 연산자 | |
| 변환 어려움 | Promise 객체 | 폴리필 필요 |
| Map, Set, WeakMap, WeakSet | 폴리필 필요 | |
Array.prototype.includes 등 새 메서드 | 폴리필 필요 | |
| async/await | 폴리필 필요 |
변환이 어려운 경우, 트랜스파일 외에 폴리필이라는 다른 기능이 필요하다.
5.1.3.3 번들러와 함께 사용하기
바벨을 단독으로 사용할 때는 다음과 같은 제약이 있다.
1. 모듈 시스템 변환 문제
- 브라우저는 Node.js의
require()와module.exports를 지원하지 않음 @babel/preset-env에서targets.esmodules: false설정 시 ES6 모듈이 CommonJS로 변환됨- 브라우저에서
require is not defined오류 발생 가능 - 반대로 설정하지 않으면
import/export를 변환하지 못해 구형 브라우저 지원 불가
2. 최적화 문제
- 코드 분할 불가: 바벨은 단독으로 코드 분할을 지원하지 않아 초기 로드 시간이 길어질 수 있음
- 다른 파일 형식 미지원: 바벨은 오직 자바스크립트만 변환하므로 CSS, HTML, 이미지 등은 관리할 수 없음
해결책: 번들러와 함께 사용
이러한 문제들로 바벨을 단독으로 사용하기보다 **웹팩(Webpack)**이나 롤업(Rollup) 같은 모듈 번들러와 함께 사용한다.
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader", // 바벨을 웹팩 로더로 사용
},
},
],
},
};5.2 폴리필을 도와주는 도구, core-js
폴리필이 필요한 이유
Promise를 예로 들면, 다음과 같은 인터페이스를 모두 구현해야 한다.
class Promise {
constructor(executor: (resolve: Function, reject: Function) => void): Promise;
then(onFulfilled: Function, onRejected: Function): Promise;
catch(onRejected: Function): Promise;
finally(onFinally: Function): Promise;
static all(iterable: Iterable): Promise;
static any(promises: Iterable): Promise;
static race(iterable: Iterable): Promise;
static reject(r: any): Promise;
static resolve(x: any): Promise;
static withResolvers(): { promise: Promise; resolve: Function; reject: Function };
}Promise의 주요 특징
| 메서드/특징 | 설명 |
|---|---|
| 생성자 함수 | executor 콜백을 받아 비동기 작업 실행. resolve/reject로 상태 결정 |
| then | 이행(fulfilled) 시 호출될 콜백 등록. 새 Promise 반환 |
| catch | 거부(rejected) 시 호출될 콜백 등록. then(null, onRejected)의 축약 |
| finally | 이행/거부 여부와 관계없이 항상 실행되는 콜백 등록 |
| 체이닝 | then/catch/finally가 Promise를 반환하여 연속 호출 가능 |
| Promise.all | 모든 Promise가 이행되면 이행, 하나라도 거부되면 즉시 거부 |
| Promise.allSettled | 모든 Promise가 완료(이행 또는 거부)될 때까지 대기 |
| Promise.race | 가장 먼저 완료되는 Promise의 결과를 반환 |
| Promise.resolve | 주어진 값을 이행 상태의 Promise로 감싸서 반환 |
| Promise.reject | 주어진 이유를 거부 상태의 Promise로 감싸서 반환 |
이렇게 많은 특징을 갖는 Promise는 낮은 ES 버전의 문법만으로는 완벽히 대체하기 어렵다. 따라서 Promise의 모든 인터페이스를 전역에 새롭게 정의해야 한다.
폴리필 구현 예시
if (!window.Promise) {
window.Promise = function (executor) {
var PENDING = "pending";
var FULFILLED = "fulfilled";
var REJECTED = "rejected";
var state = PENDING;
var value = undefined;
var handlers = [];
function resolve(result) {
if (state !== PENDING) return;
state = FULFILLED;
value = result;
handlers.forEach(handle);
}
function reject(error) {
if (state !== PENDING) return;
state = REJECTED;
value = error;
handlers.forEach(handle);
}
function handle(handler) {
if (state === PENDING) {
handlers.push(handler);
} else if (state === FULFILLED && typeof handler.onFulfilled === "function") {
handler.onFulfilled(value);
} else if (state === REJECTED && typeof handler.onRejected === "function") {
handler.onRejected(value);
}
}
this.then = function (onFulfilled, onRejected) {
return new Promise(function (resolve, reject) {
handle({
onFulfilled: function (value) {
try {
resolve(onFulfilled ? onFulfilled(value) : value);
} catch (e) {
reject(e);
}
},
onRejected: function (error) {
try {
resolve(onRejected ? onRejected(error) : error);
} catch (e) {
reject(e);
}
},
});
});
};
executor(resolve, reject);
};
}폴리필(Polyfill): 트랜스파일만으로 해결되지 않는 기능을 지원하기 위해, 동일한 이름으로 구형 브라우저에서도 작동하도록 전역에 정의되는 메서드나 객체
폴리필을 제공하는 대표적인 라이브러리가 core-js이며, 바벨은 이를 프리셋과 플러그인에서 설정할 수 있게 지원한다.
5.2.1 core-js란 무엇인가?
자바스크립트 폴리필 라이브러리로, 최신 ECMAScript 표준과 아직 표준으로 채택되지 않은 제안 기능까지 지원
특징
| 특징 | 설명 |
|---|---|
| 폴리필 제공 | Promise, Symbol, Map, Set 등 다양한 기능의 폴리필 제공 |
| 모듈화 | 필요한 폴리필만 선택적으로 가져올 수 있음 |
| 제안 단계 지원 | TC39 제안 단계(Stage 0~4)의 실험적 기능도 지원 |
폴리필로 해결할 수 없는 기능
Reflect.construct- 꼬리 호출 최적화 (Tail Call Optimization)
- 일부 심볼 기능
- 클래스의 비공개 필드와 메서드 (
#privateField)
이러한 기능은 자바스크립트 엔진 수준에서 깊은 통합과 최적화가 필요하기 때문에 폴리필로 구현하기 어렵다.
core-js 임포트 방법
// 방법 1: 모든 안정적인 폴리필 임포트 (어떤 폴리필이 필요한지 모를 때)
import "core-js/stable";
// 방법 2: 필요한 폴리필만 선택적으로 임포트
import "core-js/features/promise";
import "core-js/features/array/includes";5.2.2 바벨과 core-js
일반적으로 바벨의 트랜스파일 기능과 함께 사용한다. 바벨은 core-js를 의존성으로 포함해서 프리셋과 플러그인에서 폴리필 기능을 설정할 수 있다.
바벨을 통해 core-js를 사용하면 개발자가 직접 어떤 폴리필이 필요한지 결정하지 않아도 되어 훨씬 편리하다.
5.2.2.1 @babel/preset-env에 core-js 설정하기
{
"presets": [
[
"@babel/preset-env",
{
"targets": { "browsers": ["> 1%", "last 2 versions"] },
"useBuiltIns": "usage",
"corejs": 3
}
]
]
}주요 설정 옵션
| 옵션 | 설명 |
|---|---|
| corejs | 사용할 core-js 버전 지정 (예: 3 또는 3.25). 정확한 버전 지정 권장 |
| targets.browsers | 지원할 브라우저 범위 지정. 이 범위에 맞춰 필요한 폴리필만 포함됨 |
| useBuiltIns | 폴리필 삽입 방식 설정 |
useBuiltIns 옵션 상세
| 값 | 설명 |
|---|---|
false | 폴리필을 자동으로 추가하지 않음 (기본값) |
"entry" | 엔트리 파일의 import "core-js" 를 타겟에 맞게 필요한 폴리필로 대체 |
"usage" | 각 파일에서 실제로 사용하는 기능의 폴리필만 자동 삽입 (권장) |
5.2.2.2 런타임에 core-js 폴리필 로드하기
core-js를 직접 임포트하거나 프리셋에서 설정하는 방식이 바람직하지 않은 경우가 있다.
문제점
- 큰 번들 크기: 사용하지 않는 폴리필까지 포함될 수 있음
- 전역 네임스페이스 오염:
Array.prototype.includes등 전역 객체를 수정함
해결책: @babel/plugin-transform-runtime
전역 오염 없이 core-js를 사용하며, 헬퍼 함수를 모듈로 가져와 사용할 수 있다.
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime-corejs3{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 3
}
]
]
}두 방식 비교
| 특징 | @babel/preset-env + corejs | @babel/plugin-transform-runtime |
|---|---|---|
| 전역 오염 | O (전역 객체 수정) | X (모듈로 가져옴) |
| 번들 크기 | 크기가 클 수 있음 | 필요한 것만 포함하여 작음 |
| 설정 난이도 | 쉬움 | 약간 복잡 |
| 의존성 | core-js | @babel/runtime-corejs3 |
| 사용 권장 | 일반 애플리케이션 | 라이브러리 개발 |
- 자바스크립트의 낮은 ES버전의 기능만 지원하는 브라우저에서 효율적으로 지원하기 위해 트랜스파일 뿐만 아니라 폴리필도 필요하다
- 대표적인 폴리필 패키지는 core-js이고 바벨과 함께 사용하는 방법을 알아보았다.
- 바벨을 사용하면 ECMAScript 호환성을 트랜스파일과 폴리필로 효율적으로 관리할 수 있다.
5.3 최선의 폴리필과 트랜스파일은 무엇일까?
| 용어 | 설명 |
|---|---|
| 폴리필 | 최신 웹 기술을 지원하지 않는 브라우저에서 해당 기능을 사용할 수 있도록 해주는 코드 |
| 트랜스파일 | 최신 자바스크립트 코드를 구형 브라우저에서도 동작할 수 있게 변환하는 것 |
어떤 특징이 폴리필과 트랜스파일을 설정하는데 영향을 미칠까?
5.3.1 지원 환경 명시하기
- 주요한 원인은 지원해야하는 브라우저 범위와 Node.js버전 범위
- 프로젝트가 목표로하는 브라우저를 명확히 하면 불필요한 폴리필과 트랜스파일을 줄일수 있어 번들 크기를 최소화할수있다.
- 브라우저 범위를 명시하는 방법으로 browserslist에 대해알아보자
5.3.1.1 browserslist
다양한 프런트엔드 도구들이 공통된 브라우저 및 Node.js 버전 설정을 공유할 수 있게 해주는 도구.
caniuse-lite데이터를 사용해 브라우저 지원 정보를 파악- Babel, ESLint, Autoprefixer, stylelint, webpack, parcel, rollup 등에서 활용
- 프로젝트 전체에서 일관된 브라우저 호환성 유지
설정 방법
package.json에서 "browserslist" 필드를 통해서 설정하거나 .browserslistrc 라는 별도의 구성 파일을 생성해서 지원 범위 관리
{
"name": "project",
"browserslist": ["defaults and fully supports es6-module", "maintained node versions"]
}# .browserslistrc
defaults and fully supports es6-module
maintained node versions쿼리 종류
| 기준 | 쿼리 예시 | 설명 |
|---|---|---|
| 시장점유율 | > 5%, >= 5% | 전세계 사용 통계 기준 |
> 5% in US | 특정 국가 기준 | |
> 5% in alt-AS | 특정 대륙 기준 | |
| 최신 버전 | last 2 versions | 모든 브라우저의 최신 2개 버전 |
last 2 Chrome versions | 특정 브라우저의 최신 2개 버전 | |
| 버전 지정 | Chrome > 100, ie 11 | 특정 브라우저의 특정 버전 |
| 기능 지원 | supports es6-module | ES6 모듈 지원 브라우저 |
fully supports css-grid | CSS Grid 완전 지원 브라우저 | |
| 유지보수 | dead | 24개월 이상 업데이트 없는 브라우저 |
not dead | 유지보수 중인 브라우저 | |
| Node.js | node 16, current node | 특정 버전 또는 현재 LTS |
maintained node versions | 유지보수 중인 모든 버전 |
기본 설정 (defaults)
> 0.5%
last 2 versions
Firefox ESR
not dead쿼리 조합
여러 쿼리를 조합하면 더 정확한 지원 범위 설정 가능
| 연산자 | 의미 | 예시 |
|---|---|---|
or, , | 합집합 (하나라도 만족) | > 1%, last 2 versions |
and | 교집합 (모두 만족) | > 0.5% and last 2 versions |
not | 제외 | not ie 11, not dead |
예시: ES5를 지원하는 브라우저 타겟팅
{
"name": "ex",
"browserslist": [">= 0.5%", "last 2 versions", "Firefox ESR", "not dead", "ie 11"]
}| 쿼리 | 설명 |
|---|---|
>= 0.5% | 시장 점유율 0.5% 이상인 브라우저 |
last 2 versions | 각 브라우저의 최신 2개 버전 |
Firefox ESR | 파이어폭스 확장 지원 릴리스 |
not dead | 유지보수 중인 브라우저만 |
ie 11 | IE 11 포함 (ES5 지원 보장) |
참고: browsersl.ist에서 쿼리 결과를 미리 확인할 수 있다.
데이터 업데이트 (update-browserslist-db)
browserslist 사용 중 다음과 같은 경고가 나타날 수 있다.
Browserslist: caniuse-lite is outdated.
Please run: npx browserslist@latest --update-db이 경고는 caniuse-lite의 브라우저 사용 통계나 버전 정보가 오래되었다는 의미.
정기적인 업데이트를 통해 최신 브라우저 정보를 반영하면, 프로젝트에서 실제로 필요한 트랜스파일과 폴리필만 적용할 수 있다.
5.3.1.2 core-js-compat
browserslist로 설정한 지원 범위에 알맞은 폴리필 코드만 추가하는 패키지. core-js 라이브러리와 함께 사용된다.
5.4 바벨과 core-js의 대안
5.4.1 타입스크립트 컴파일러
트랜스파일은 Babel 대신 TypeScript 컴파일러를 사용할 수 있다.
- 정적 타입 검사뿐만 아니라 트랜스파일까지 수행
tsconfig.json의target필드로 대상 ECMAScript 버전 지정target: "ES5"로 설정하면 ES6+ 코드를 ES5로 변환
{
"compilerOptions": {
"target": "ES5"
}
}target필드에서 사용 가능한 ECMAScript버전은 TypeScript 5버전을 기준으로 다음과 같다.
| target | 설명 |
|---|---|
| ES3 | 1999년 표준. 가장 오래된 버전 |
| ES5 | 대부분의 현대 브라우저에서 널리 지원되며, IE 11과 같은 구형 브라우저와 호환 |
| ES6/ES2015 | let, const, 화살표 함수, 클래스, 모듈 같은 최신 기능 포함 |
| ES2016 ~ ES2022 | ES6에서 추가 기능이 더해진 확장 버전. 특정 연도로 설정하면 그 이후 버전의 기능 사용 불가 |
| ESNext | 최신 ECMAScript 제안 기능 대상. TC39의 Stage 3 제안 기능까지 포함 |
⚠️ target을 지정하지 않으면 불필요하게 구형 브라우저까지 호환되도록 트랜스파일되어 번들 크기가 커지므로 주의
5.4.1.1 트랜스파일에 영향을 주는 옵션들
module : 타입스크립트 컴파일러가 코드를 변환할 때 사용할 모듈 시스템을 결정하는 필드
| module | 설명 |
|---|---|
| none | 모듈 시스템 미사용. 전역 스코프에서 동작하는 스크립트 작성 시 사용 |
| preserve | import/export 구문을 변환하지 않고 그대로 유지. 번들러가 모듈 처리 담당 시 사용 |
| node16 | Node.js 16+ 모듈 시스템. package.json의 type 필드에 따라 ESM/CJS 자동 결정 |
| nodenext | Node.js 최신 모듈 시스템. node16과 유사하나 향후 Node.js 변경사항 반영 |
| commonjs | CommonJS로 변환. Node.js 환경에서 사용 |
| amd | AMD(Asynchronous Module Definition)로 변환. RequireJS 환경 |
| umd | UMD(Universal Module Definition)로 변환. 범용 모듈 패턴 |
| system | SystemJS 모듈 로더용으로 변환 |
| es6/es2015 | ES6 모듈 구문(import/export)으로 출력. 모던 브라우저나 번들러 환경 |
| es2020 | 동적 import()와 import.meta 지원 |
| es2022 | top-level await 지원 |
| esnext | 최신 ECMAScript 모듈 기능 모두 지원. 가장 최신의 모듈 문법 사용 가능 |
lib : 컴파일할 때 포함할 표준 라이브러리의 목록을 지정하는 필드. 사용할 수 있는 기능과 API의 범위를 정의한다.
환경 라이브러리
| 값 | 설명 |
|---|---|
| DOM | 브라우저의 DOM API |
| WebWorker | 웹워커 API |
| ScriptHost | WSH(Windows Script Host) API |
| WebGL | WebGL API |
특정 API만 추가하고 싶다면
ESNext.Promise,DOM.Iterable처럼 세부 API를 지정할 수도 있다.
{
"compilerOptions": {
"target": "ES5",
"lib": ["ESNext", "DOM"]
}
}lib는 주로 타입 검사와 관련이 있고, 트랜스파일이나 폴리필과는 직접적으로 관계가 없지만 간접적으로 영향을 미칠 수 있다.
{
"compilerOptions": {
"target": "ES5",
"lib": ["ES2017"]
}
}위 설정에서 lib: ES2017으로 Promise와 async/await을 사용할 수 있지만, target: ES5이므로 ES5 문법으로 변환된다.
| 기능 | 처리 방식 |
|---|---|
| async/await | ES5 코드로 트랜스파일 |
| Promise | 폴리필 추가 필요 |
lib는 사용 가능한 기능의 범위를 정의하지만, 트랜스파일과 폴리필 관점에서 추가 작업이 필요할 수 있다.
sourceMap : 컴파일된 자바스크립트 파일에 소스맵을 생성할지 여부를 설정하는 필드.
소스맵은 원본 TypeScript 코드와 트랜스파일된 JavaScript 코드 간의 매핑을 제공하는 객체 형태의 데이터다.
소스맵의 장점
| 기능 | 설명 |
|---|---|
| 디버깅 | 브라우저 개발 도구에서 원본 TypeScript 코드의 위치를 정확히 확인 가능 |
| 에러 추적 | 오류 스택 추적에서 JS 위치를 TS 위치로 매핑 |
| 원본 코드 연결 | 압축/난독화된 코드에서도 원본 코드 정보 제공 |
소스맵 파일 구조 (.js.map)
{
"version": 3,
"file": "index.js",
"sourceRoot": "",
"sources": ["../src/index.tsx"],
"names": [],
"mappings": "AAAA;IAAA;..."
}| 필드 | 설명 |
|---|---|
| version | 소스맵 파일의 버전 |
| file | 트랜스파일된 파일 이름 |
| sources | 원본 파일 이름의 배열 |
| sourceRoot | 원본 파일 경로의 기본 디렉터리 |
| mappings | 트랜스파일된 코드와 원본 코드 간의 매핑 정보 (인코딩된 문자열) |
참고: source-map-visualization으로 소스맵을 시각화할 수 있다.
환경별 설정
소스맵은 디버깅에 유용하지만, 프로덕션에서는 번들 크기를 늘리므로 환경별로 다르게 설정해야 한다.
{
"scripts": {
"build:dev": "tsc --sourceMap true",
"build:prod": "tsc --sourceMap false"
}
}jsx : React처럼 JSX 문법을 사용하는 프로젝트에서 TypeScript 컴파일러가 JSX를 올바르게 트랜스파일하도록 설정하는 필드.
| jsx | 설명 | React import |
|---|---|---|
| preserve | JSX 구문을 변환하지 않고 그대로 유지. Babel 등 별도 도구로 최종 변환 | - |
| react | React.createElement() 호출로 변환. React 16 이하 호환 | 필수 |
| react-jsx | react/jsx-runtime 사용. React 17+ 새로운 변환 방식 | 불필요 |
| react-jsxdev | react-jsx와 유사하나 개발 환경용 디버깅 정보 포함 | 불필요 |
| react-native | React Native의 React.createElement 형태로 변환 | 필수 |
tslib : TypeScript 런타임 라이브러리. 중복되는 헬퍼 함수들을 외부 라이브러리로 추출해서 코드 중복을 줄이고 번들 크기를 최적화한다.
Babel의 @babel/plugin-transform-runtime과 유사한 역할을 수행하지만 차이점이 있다.
tslib vs @babel/plugin-transform-runtime
| 비교 항목 | tslib | @babel/plugin-transform-runtime |
|---|---|---|
| 폴리필 처리 | 폴리필 기능 없음. 별도로 core-js 등 추가 필요 | core-js와 연동하여 폴리필 자동 주입 가능 |
| 유연성 | TypeScript 전용. 설정이 단순함 | 다양한 플러그인과 프리셋으로 세밀한 커스터마이징 가능 |
| 사용 환경 | TypeScript 프로젝트 전용 | JavaScript/TypeScript 모두 사용 가능 |
이러한 차이점을 고려하여 프로젝트에 맞는 폴리필 관리 전략을 수립해야 한다.
5.4.1.3 타입스크립트 컴파일러와 바벨 비교
| 비교 항목 | TypeScript 컴파일러 | Babel |
|---|---|---|
| 폴리필 | 내장 플러그인 없음. 별도 설정 필요 | @babel/preset-env로 자동 폴리필 |
| 헬퍼 함수 | tslib 패키지 제공 | @babel/plugin-transform-runtime |
| 플러그인 생태계 | 제한적 | 풍부한 플러그인으로 다양한 커스터마이징 가능 |
| 컴파일 속도 | 타입 검사로 인해 대규모 프로젝트에서 상대적으로 느림 | 타입 검사 없이 빠른 트랜스파일 |
5.4.2 SWC (Speedy Web Compiler)
Rust로 개발된 고성능 트랜스파일러. Babel과 비교해서 상당히 빠른 속도를 자랑하며, Rust의 메모리 안전성과 뛰어난 성능 덕분에 빌드 속도가 중요한 대규모 프로젝트에서 특히 유용하다.
Next.js, Vite, Parcel, Deno 등에서 SWC를 사용해 TypeScript를 빠르게 트랜스파일한다.
5.4.2.1 SWC의 특징
| 특징 | 설명 |
|---|---|
| Babel 호환성 | 유사한 설정 방식으로 기존 Babel 설정을 쉽게 전환 가능. 사용자 정의 플러그인 시스템도 지원 |
| 트리 셰이킹 | 사용되지 않는 코드를 제거해 최종 번들 크기 최적화 |
| TypeScript 통합 | 타입 검사 없이 빠르게 트랜스파일 가능 |
| 공식 JSON 스키마 | IDE에서 자동완성, 유효성 검사 및 정적 분석 지원 |
| 폴리필 지원 | core-js 내장 가능, browserslist 통합, 다양한 모듈 시스템 및 압축 지원 |
5.4.2.2 SWC로 트랜스파일하기
설치
npm install -D @swc/cli @swc/core설정 파일 (.swcrc)
jsc : JavaScript Compiler 옵션을 정의하는 핵심 설정
| 옵션 | 설명 |
|---|---|
| parser | 파서 설정. syntax: "typescript" 또는 "ecmascript", JSX/TSX 지원 여부 등 |
| target | 트랜스파일 대상 ECMAScript 버전. "es5", "es2015" ~ "es2022", "esnext" |
| transform | 코드 변환 옵션. React JSX 변환, decorator, const enum 등 설정 |
| minify | 코드 압축 활성화 여부. true로 설정 시 minification 수행 |
| keepClassNames | 클래스 이름 유지 여부. 디버깅이나 리플렉션에 유용 |
module : 출력 모듈 시스템 설정
| 값 | 설명 |
|---|---|
| es6 | ES6 모듈 (import/export) |
| commonjs | CommonJS 모듈 (require/module.exports) |
| umd | UMD (Universal Module Definition) |
| amd | AMD (Asynchronous Module Definition) |
| nodenext | Node.js 최신 모듈 시스템 |
env : 폴리필 및 브라우저 타겟 설정 (Babel의 @babel/preset-env와 유사)
| 옵션 | 설명 |
|---|---|
| targets | 지원할 브라우저/환경 지정. browserslist 쿼리 또는 객체 형태 (예: { "chrome": "80" }) |
| mode | 폴리필 주입 방식. "entry" (진입점에 전체 주입), "usage" (사용된 기능만 주입) |
| coreJs | 사용할 core-js 버전 지정. "3.30" 형태로 마이너 버전까지 명시 권장 |
@swc/helpers : Babel의 @babel/runtime과 유사한 헬퍼 함수 라이브러리. 중복되는 헬퍼 코드를 외부 모듈로 분리해 번들 크기를 최적화한다. jsc.externalHelpers: true 설정과 함께 사용.
5.4.2.3 SWC와 바벨, 타입스크립트 컴파일러 비교
| 비교 항목 | SWC | Babel | TypeScript |
|---|---|---|---|
| 설정 방식 | .swcrc (tsconfig.json과 유사) | .babelrc / babel.config.js | tsconfig.json |
| 성능 | Rust 기반으로 매우 빠름 | JavaScript 기반, 상대적으로 느림 | 타입 검사로 인해 느릴 수 있음 |
| 폴리필 | env 필드로 core-js 통합 | @babel/preset-env로 자동 주입 | 별도 설정 필요 |
| 플러그인 생태계 | 성장 중 (Rust로 작성 필요) | 매우 풍부함 | 제한적 |
SWC의 장점
tsconfig.json과 유사한 설정 방식으로 간편함- Babel과 거의 완벽한 호환성 + 뛰어난 성능
env필드로 폴리필 기능 지원
SWC의 도전 과제
- 플러그인 생태계가 Babel만큼 풍부하지 않음 (사용자 정의 플러그인은 Rust로 작성 필요)
- 일부 도구/프레임워크와 완벽히 호환되지 않을 수 있음
- 예: MobX의 데코레이터가 완벽하게 동작하지 않았던 사례
- Babel은
@babel/plugin-proposal-decorators로 안정적 지원
현재 SWC는
jsc.parser.decorators옵션으로 최신 ECMAScript 표준에 따른 데코레이터를 지원하지만, 완전한 호환은 어려울 수 있음을 유의해야 한다.
@swc/wasm-typescript패키지 배포 등 SWC는 계속 발전 중이며, 플러그인 생태계도 성장할 잠재력이 충분하다.
5.4.3 es-shims
core-js를 대신 사용할 수 있는 폴리필 프로젝트. ECMAScript Shims (이하 es-shims)
브라우저나 런타임 환경에서 부족한 부분을 보완하기 위해 폴리필을 제공하는 오픈소스 프로젝트로, JavaScript 기능을 하위 호환성을 지원하지 않는 환경에서도 사용할 수 있게 하는 것을 목표로 한다.
5.4.3.1 폴리필을 로드하는 방식
core-js 방식
targetProperty가 undefined인지 확인하고, undefined인 경우에만 폴리필을 적용한다.
es-shims 방식
define-properties 유틸리티 라이브러리를 사용해 프로퍼티 추가 여부를 동적으로 결정한다.
// Array.prototype.findLast 폴리필 예시
function () {
return Array.prototype.findLast !== polyfill;
}위 함수는 Array.prototype.findLast가 이미 존재하고, 그 메서드가 polyfill과 다를 경우 네이티브 메서드로 판단한다.
두 라이브러리 모두 실행 시점에 브라우저가 해당 기능을 지원하는지 검사하고, 필요할 때만 폴리필을 적용한다.
5.4.3.2 모듈화된 설계
es-shims와 core-js의 가장 큰 차이는 사용 방식이다.
| 비교 항목 | es-shims | core-js |
|---|---|---|
| 구조 | 기능별로 개별 패키지 분리 | 하나의 큰 패키지에 모든 폴리필 포함 |
| 설치 | 필요한 기능만 개별 설치 | 전체 설치 후 필요한 것만 import |
| 번들 크기 | 사용하는 기능만큼만 포함 | 트리 셰이킹에 의존 |
# es-shims: 필요한 폴리필만 개별 설치
npm install array.prototype.findlast
npm install object.fromentries
# core-js: 전체 설치 후 선택적 import
npm install core-js5.4.3.3 레거시 환경 지원에 최적화된 es-shims
es-shims는 안정적이고 검증된 기능의 폴리필에 초점을 맞춘다.
- ECMAScript 표준 준수와 호환성을 최우선으로 고려
- 새로운 기능보다는 기존 사양의 안정성 강화에 중점
- TC39 Stage 4(정식 표준)에 도달한 기능 위주로 지원
5.4.4 Polyfill.io
또 다른 폴리필 솔루션. CDN 기반으로 폴리필을 제공하는 서비스다.
5.4.4.1 폴리필을 로드하는 방식
core-js나 es-shims와 달리, Polyfill.io는 서버 사이드에서 User-Agent를 분석하여 브라우저에 필요한 폴리필만 동적으로 제공한다.
<script src="https://polyfill.io/v3/polyfill.min.js"></script>| 비교 항목 | core-js / es-shims | Polyfill.io |
|---|---|---|
| 폴리필 결정 시점 | 빌드 타임 또는 런타임 | 요청 시점 (서버에서) |
| 판단 기준 | 코드 내 feature detection | User-Agent 헤더 분석 |
| 번들 포함 | 포함됨 | 외부 CDN에서 로드 |
5.4.4.2 서빙 방법
쿼리 파라미터로 필요한 기능 지정 가능
<!-- 특정 기능만 요청 -->
<script src="https://polyfill.io/v3/polyfill.min.js?features=Promise,Array.prototype.includes"></script>
<!-- 플래그 옵션 -->
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6&flags=gated"></script>| 옵션 | 설명 |
|---|---|
features | 필요한 폴리필 기능 지정 (쉼표로 구분) |
flags=gated | feature detection 후 필요한 경우에만 적용 |
flags=always | 항상 폴리필 적용 (브라우저 지원 여부 무관) |
callback | 폴리필 로드 완료 후 실행할 콜백 함수 지정 |
주의: 2024년 Polyfill.io 도메인이 악성 코드를 배포하는 사건이 발생했다. 현재는 Cloudflare나 Fastly에서 제공하는 대체 CDN(
cdnjs.cloudflare.com/polyfill)을 사용하거나, 자체 호스팅하는 것을 권장한다.