위 글은 NPM DEEP DIVE를 읽고, 그 중 5장의 내용을 정리하며,
gpt와 티키타카한 글이다.
Babel
트랜스파일의 필요성
- ES6 이후 구형브라우저와 호환성을 유지하기 위해 트랜스파일러의 필요성이 대두됨
- 초기에는 ES6코드를 ES5로 트랜스파일링 기능만 제공했으나, 이후 최신 ECMAScript + ESNext, 리액트 JSX 문법, 타입스크립트 등 확장 기능과 프리셋을 통해 특정 환경이나 목표에 맞는 미리 구성된 설정을 할 수 있게함
동작방식
- 추상 구문 트리(AST, Abstract Syntax Tree)는 소스코드의 구조를 트리 형태로 표현한 자료구조로, 컴팡일러와 인터프리터가 소스코 드를 분석하고 변환하는 역할을 하며, 최상위 노드, 노드, 자식 노드로 구성됨.
- 아래는 astexplorer에서 filter라는 흔한 js 메소드를 넣고 ast를 받은 결과이다.
let tips = [
"Click on any AST node with a '+' to expand it",
"Hovering over a node highlights the \
corresponding location in the source code",
"Shift click on an AST node to expand the whole subtree"
];
function printFilterEx() {
tips.filter((tip, i) => console.log(`Tip ${i}:` + tip));
}
{
// 🌳 루트 노드: 전체 파일을 나타냄
"type": "Program",
"sourceType": "script",
"body": [
{
// 🌿 1단계 노드: 변수 선언 (let tips = [...])
"type": "VariableDeclaration",
"kind": "let",
"declarations": [
{
// 🍃 2단계 노드: tips 변수 선언부
"type": "VariableDeclarator",
"id": {
// 🍂 tips라는 식별자
"type": "Identifier",
"name": "tips"
},
"init": {
// 🍂 tips에 할당된 배열
"type": "ArrayExpression",
"elements": [
{ "type": "Literal", "value": "Click on any AST node with a '+' to expand it" },
{ "type": "Literal", "value": "Hovering over a node highlights the corresponding location in the source code" },
{ "type": "Literal", "value": "Shift click on an AST node to expand the whole subtree" }
]
}
}
]
},
{
// 🌿 1단계 노드: 함수 선언 (printFilterEx)
"type": "FunctionDeclaration",
"id": {
// 🍂 함수 이름 식별자
"type": "Identifier",
"name": "printFilterEx"
},
"params": [],
"body": {
// 🍃 함수의 본문 블록
"type": "BlockStatement",
"body": [
{
// 🍂 함수 내부 표현식: tips.filter(...)
"type": "ExpressionStatement",
"expression": {
// 🍃 함수 호출 표현식: tips.filter(...)
"type": "CallExpression",
"callee": {
// 🍂 tips 객체의 filter 프로퍼티 접근
"type": "MemberExpression",
"object": {
"type": "Identifier",
"name": "tips"
},
"property": {
"type": "Identifier",
"name": "filter"
},
"computed": false
},
"arguments": [
{
// 🍃 filter에 전달된 화살표 함수
"type": "ArrowFunctionExpression",
"params": [
{ "type": "Identifier", "name": "tip" },
{ "type": "Identifier", "name": "i" }
],
"body": {
// 🍂 화살표 함수 내부: console.log(...)
"type": "CallExpression",
"callee": {
"type": "MemberExpression",
"object": { "type": "Identifier", "name": "console" },
"property": { "type": "Identifier", "name": "log" },
"computed": false
},
"arguments": [
{
// 🍁 이진 표현식: 템플릿 리터럴 + tip
"type": "BinaryExpression",
"operator": "+",
"left": {
// 🍂 템플릿 리터럴: `Tip ${i}:`
"type": "TemplateLiteral",
"quasis": [
{
"type": "TemplateElement",
"value": { "raw": "Tip ", "cooked": "Tip " },
"tail": false
},
{
"type": "TemplateElement",
"value": { "raw": ":", "cooked": ":" },
"tail": true
}
],
"expressions": [
{ "type": "Identifier", "name": "i" }
]
},
// 🍂 + 연산자의 오른쪽: tip
"right": { "type": "Identifier", "name": "tip" }
}
]
},
"expression": true
}
]
}
}
]
}
}
]
}
- 바벨의 변환 과정(파싱(@babel/parser) => 변환(@babel/traverse) => 출력(@babel/generator))
바벨 사용해보기
✅ 바벨 구성 파일
바벨은 설정을 통해 어떻게 코드를 변환할지를 지정합니다.
설정 파일은 여러 형식이 가능하며, 주요 설정 항목은 다음과 같습니다:
- 파일 형식:
babel.config.js/.babelrc.js.babelrc(JSON)package.json내부의"babel"필드
- 주요 설정 옵션:
presets: 사전 설정된 플러그인 모음 (@babel/preset-env등)plugins: 개별 플러그인 지정 (@babel/plugin-transform-arrow-functions등)env: 환경별 설정 분기 (예: dev/prod/test)ignore,only: 특정 파일 포함/제외exclude,include: 경로 기준 필터링 (Webpack 등에서 사용)sourceMaps: 소스맵 생성 여부compact,minified: 출력 결과 압축/최소화retainLines: 원래 줄 유지extends: 다른 바벨 설정 파일을 확장overrides: 특정 조건일 때 다른 설정 적용
// babel.config.js
module.exports = {
presets: ['@babel/preset-env'], // 최신 JS 문법을 구버전으로 변환
plugins: ['@babel/plugin-transform-arrow-functions'], // 화살표 함수 변환
env: {
production: {
compact: true, // 배포 환경에선 코드 압축
minified: true, // 변수명 축소 등
},
},
ignore: ['node_modules'], // 바벨 처리 제외
};
🛠️ 바벨 단독 사용
바벨은 번들러 없이도 CLI를 통해 직접 사용할 수 있습니다.
npm install --save-dev @babel/cli @babel/core
@babel/core: 바벨 핵심 기능@babel/cli: 터미널에서babel명령어 사용 가능하게 해준다.
이 경우 targets 설정을 통해 어떤 브라우저/환경을 지원할지 명시해줄 수 있습니다.
// .babelrc
{
"presets": [
["@babel/preset-env", {
"targets": {
"chrome": "60", // 크롬 60 이상을 지원
"ie": "11" // IE11 지원
}
}]
]
}
# src 디렉토리의 JS 파일을 dist로 변환
npx babel src --out-dir dist
🔗 번들러(Webpack 등)와 함께 사용
바벨은 보통 Webpack, Vite 등의 번들러와 함께 사용됩니다. 이 때 주의해야 할 이슈:
1. 모듈 시스템 변환 문제
Node.js 런타임은 ES Module을 완전히 지원하지 않기 때문에 CommonJS로 변환이 필요합니다.
# 이 문제는 "require is not defined" 에러로 나타남
해결: targets.esmodules 설정을 통해 ES Modules 변환 여부를 제어합니다.
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {
targets: {
esmodules: true // ES 모듈을 유지함 (브라우저 모듈 로딩용)
}
}]
]
}
2. 최적화 문제
바벨만으로는 코드 스플리팅, 트리 쉐이킹 등의 최적화가 불가능합니다.
=> 반드시 번들러와 함께 사용해야 합니다.
npm install -D webpack webpack-cli babel-loader @babel/core @babel/preset-env
// webpack.config.js
module.exports = {
entry: './src/index.js', // 번들 시작 지점
output: {
filename: 'bundle.js', // 출력 파일 이름
path: __dirname + '/dist'
},
module: {
rules: [
{
test: /\.js$/, // .js 파일에 대해
exclude: /node_modules/, // node_modules 제외
use: {
loader: 'babel-loader', // babel-loader로 변환
options: {
presets: ['@babel/preset-env'] // 최신 JS를 구버전으로
}
}
}
]
}
};
🧾 요약
| 방식 | 설명 | 예시 |
|---|---|---|
| 바벨 구성 파일 | 어떤 문법을 어떻게 변환할지 설정 | .babelrc, babel.config.js, package.json의 babel 필드 |
| 바벨 단독 사용 | CLI로 소스코드만 변환 | npx babel src --out-dir dist |
| 번들러와 함께 | 최적화 + 모듈 변환까지 지원 | webpack + babel-loader |
🧩 폴리필을 도와주는 도구 core-js
core-js는 ES 표준의 기능들을 폴리필(polyfill) 해주는 대표적인 라이브러리입니다.
주로 구형 브라우저나 Node 환경에서도 최신 문법과 내장 객체 기능을 사용할 수 있게 해줍니다.
🔶 Promise의 특징 (폴리필 대상이 될 수 있는 예시)
- 생성자 함수:
new Promise(...) - 메서드:
then,catch,finally - 체이닝 지원
- 정적 메서드:
Promise.all,Promise.allSettled,Promise.race,Promise.resolve,Promise.reject
// 예: Promise 사용 예시
const p = new Promise((resolve, reject) => {
setTimeout(() => resolve("완료!"), 1000);
});
p.then(result => console.log(result)) // 완료!
.catch(err => console.error(err)) // 오류 처리
.finally(() => console.log("끝!")); // 항상 실행됨
📦 core-js란?
core-js는 다음과 같은 특징을 가진 폴리필 라이브러리입니다:
- 광범위한 ES 기능 지원: Promise, Array 메서드, Map, Set 등
- 모듈화: 필요한 폴리필만 가져올 수 있음
- 직접 임포트해서 사용 가능
설치 및 사용 예시
npm install --save core-js@3
// 전체 stable 폴리필 불러오기 (전역 오염 위험 있음)
import 'core-js/stable';
// 필요한 기능만 모듈식으로 불러오기
import 'core-js/features/promise'; // Promise만 폴리필
import 'core-js/features/array/includes'; // Array.prototype.includes만 폴리필
⚙️ Babel과 core-js 연동
core-js는 Babel과 함께 사용하는 경우가 많습니다. 하지만 설정 방식에 따라 번들 크기, 전역 오염 등의 문제가 발생할 수 있습니다.
방법 1: @babel/preset-env에 core-js 설정
npm install --save-dev @babel/preset-env core-js@3
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {
useBuiltIns: 'usage', // 사용한 기능만 폴리필
corejs: 3 // core-js@3 사용
}]
]
};
// Array.prototype.includes 같은 ES 기능 사용 시,
// 필요한 폴리필만 자동으로 포함됨 (전역 오염 위험 있음)
['a', 'b'].includes('a');
이 방식은 런타임에 전역 오염 가능성이 있기 때문에 대규모 프로젝트에서는 지양합니다.
🧼 더 안전한 방법: @babel/plugin-transform-runtime
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime-corejs3
// babel.config.js
module.exports = {
presets: ['@babel/preset-env'],
plugins: [
['@babel/plugin-transform-runtime', {
corejs: 3, // core-js를 런타임에서 자동 임포트
helpers: true,
regenerator: true // async/await 등을 위한 generator 지원
}]
]
};
이 방식은 Promise, Array.includes 같은 기능이 사용될 때마다
필요한 모듈을 Babel이 자동으로 @babel/runtime-corejs3에서 임포트하도록 처리합니다.
// 바벨 플러그인을 통해 아래와 같은 코드가 생성됨 (예시)
import "core-js/modules/es.promise";
import "core-js/modules/es.array.includes";
- ✅ 장점: 번들 크기 최적화, 전역 오염 없음
- 🚫 단점:
@babel/runtime-corejs3를 runtime dependencies로 추가해야 함
🧾 요약 정리
| 항목 | 설명 |
|---|---|
core-js |
광범위한 JS 기능에 대한 폴리필을 제공하는 라이브러리 |
| 직접 임포트 | import 'core-js/features/...' 방식으로 필요한 기능만 폴리필 가능 |
@babel/preset-env + core-js |
코드에 사용된 기능만 자동 폴리필 (전역 오염 위험 있음) |
@babel/plugin-transform-runtime + core-js |
전역 오염 없이 모듈 임포트로 안전하게 폴리필 처리 |
🧠 최선의 폴리필과 트랜스파일은 무엇일까?
최신 JavaScript 코드를 모든 브라우저나 런타임 환경에서 잘 작동하게 하기 위해,
우리는 두 가지 작업이 필요합니다:
- 트랜스파일(Transpile): 최신 문법을 구버전 문법으로 바꾸는 것
- 폴리필(Polyfill): 브라우저가 지원하지 않는 내장 API나 메서드를 추가해주는 것
📌 1. 지원 환경 명시하기 (browserslist)
browserslist는 어떤 브라우저들을 지원할지 명시하는 도구이며,@babel/preset-env, Autoprefixer, ESLint 등과 함께 사용됩니다.
설정 방법
// package.json
{
"browserslist": [
"> 0.5%", // 전 세계 사용자 점유율이 0.5% 이상인 브라우저
"last 2 versions", // 각 브라우저의 최근 2개 버전
"not dead" // 공식적으로 종료되지 않은 브라우저
]
}
CLI 도구들
npx update-browserslist-db # 브라우저 사용 통계 최신화
npm install -D browserslist-config-mycompany
// 공유 config 사용
{
"browserslist": ["extends browserslist-config-mycompany"]
}
🛠 2. @babel/preset-env의 targets 필드
targets는 어느 브라우저를 목표로 트랜스파일할지를 명시합니다.
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {
targets: {
chrome: "58", // 크롬 58 이상
ie: "11" // IE11까지 지원
},
useBuiltIns: 'usage',
corejs: 3
}]
]
};
useBuiltIns: 'usage': 사용한 기능만 폴리필corejs: 3:core-js@3기준으로 폴리필 적용
🧪 3. core-js-compat를 통한 폴리필 분석
core-js-compat는 특정 환경에서 어떤 폴리필이 필요한지 분석해주는 도구입니다.
npx core-js-compat --targets "ie 11, chrome 50"
→ 어떤 core-js 기능들이 필요한지 알려줌
🔧 4. @babel/plugin-transform-runtime과 함께 쓰기
전역 오염 없이 폴리필을 주입하고 싶을 때 사용하는 플러그인입니다.browserslist와 함께 사용하면 목표 브라우저에 필요한 코드만 포함시킬 수 있습니다.
npm install --save @babel/runtime-corejs3
npm install --save-dev @babel/plugin-transform-runtime
// babel.config.js
plugins: [
['@babel/plugin-transform-runtime', {
corejs: 3,
regenerator: true
}]
]
⚠️ 5. 폴리필의 올바른 주입 방법
❌ 문제점: core-js 버전 자동주입의 한계
- Babel은 core-js를 자동으로 주입하지 않음
- 수동 임포트하거나, 프리셋/플러그인으로 직접 설정해야 함
✅ 해결: babel-plugin-polyfill-corejs3
npm install --save-dev babel-plugin-polyfill-corejs3
// babel.config.js
plugins: [
['babel-plugin-polyfill-corejs3', {
method: 'usage-global', // 글로벌 폴리필 방식
targets: "> 0.25%, not dead"
}]
]
usage-global: 사용된 기능 기반으로 필요한 폴리필만 포함- 번들 크기 최적화 + 버전 오염 방지
🎯 6. 어떤 브라우저를 지원해야 할까? (지원 범위 선정 기준)
| 기준 | 설명 |
|---|---|
| 서비스 대상 국가 | 특정 국가 사용자들이 주로 사용하는 브라우저 확인 |
| 유저 분석 도구 | Google Analytics, StatCounter 등에서 실제 사용 통계 확인 |
| 유지보수 고려 | 오래된 브라우저 지원 시 테스트 및 버그 대응 비용 증가 |
| 성능 고려 | 최신 브라우저만 지원하면 더 가벼운 번들 생성 가능 |
// 예시: 대한민국 사용자 기준
{
"browserslist": [
"last 2 Chrome versions",
"last 2 Safari versions",
"ie 11",
"not op_mini all"
]
}
🧾 요약
| 항목 | 설명 |
|---|---|
browserslist |
어떤 브라우저를 지원할지 정의하는 기준 |
@babel/preset-env |
targets와 연동해 필요한 트랜스파일/폴리필 자동 적용 |
core-js-compat |
어떤 기능이 폴리필 대상인지 분석 가능 |
transform-runtime |
전역 오염 없이 안전하게 core-js 기능 주입 |
babel-plugin-polyfill-corejs3 |
사용한 기능만 정확하게 폴리필 주입 |
🚀 바벨과 core-js의 대안
📘 1. TypeScript 컴파일러 (tsc)
타입스크립트는 정적 타입 검사 외에도 트랜스파일 기능을 제공합니다. 바벨을 쓰지 않아도 최신 문법을 구버전으로 변환할 수 있습니다.
✅ 주요 설정 옵션
// tsconfig.json
{
"compilerOptions": {
"target": "ES5", // 트랜스파일 대상 JS 버전
"module": "ESNext", // 모듈 시스템 (CommonJS, ESNext 등)
"lib": ["DOM", "ES2015"], // 사용할 내장 객체 (API 지원 결정)
"sourceMap": true, // 디버깅을 위한 소스맵 생성
"jsx": "react-jsx", // JSX 처리 방식 (React 프로젝트 시)
"importHelpers": true, // tslib 사용 여부
"esModuleInterop": true, // CJS/ESM 호환
"moduleResolution": "node" // 모듈 해석 방식
}
}
📦 tslib
tslib는 TypeScript가 공통적으로 사용하는 헬퍼 함수들의 모음importHelpers: true사용 시 트랜스파일된 결과에tslib를 활용해 중복 코드 감소
npm install tslib
🆚 TypeScript vs Babel
| 항목 | TypeScript (tsc) |
Babel |
|---|---|---|
| 타입 검사 | ✅ 있음 | ❌ 없음 |
| 빠른 컴파일 | ⛔ 상대적으로 느림 | ✅ 빠름 (AST 최적화) |
| 최신 문법 지원 | ✅ (목표 target에 맞게 변환) |
✅ (더 풍부한 플러그인 생태계) |
| 폴리필 지원 | ❌ 없음 (별도 설정 필요) | ✅ @babel/preset-env로 지원 가능 |
⚡ 2. SWC (Speedy Web Compiler)
Rust로 작성된 초고속 트랜스파일러로, Babel의 대안으로 주목받고 있습니다.
🌟 주요 특징
| 항목 | 설명 |
|---|---|
| 빠른 속도 | Babel 대비 10~20배 빠른 컴파일 |
| 바벨과 호환성 | 대부분의 Babel 플러그인/프리셋과 호환되도록 설계 |
| 타입스크립트 통합 | TS 문법 직접 지원 (타입 제거만, 타입 검사 불가) |
| 트리 셰이킹 | ES 모듈 트리 셰이킹 지원 (Webpack/Rollup과 함께 사용 시) |
| 공식 스키마 | .swcrc JSON 설정 스키마 제공 |
⚙️ 설치 및 활용법
npm install --save-dev @swc/core @swc/cli
// .swcrc
{
"jsc": {
"parser": {
"syntax": "typescript", // "ecmascript" 또는 "typescript"
"tsx": true
},
"transform": {
"react": {
"runtime": "automatic"
}
},
"target": "es5"
},
"module": {
"type": "es6" // 또는 "commonjs"
},
"env": {
"targets": {
"chrome": "58",
"ie": "11"
}
}
}
📌 ts-node + swc 사용
npm install -D @swc/register ts-node
// 실행 시
node -r @swc/register your-script.ts
🆚 SWC vs Babel vs TypeScript
| 항목 | Babel | SWC | TypeScript Compiler |
|---|---|---|---|
| 속도 | 보통 | 매우 빠름 | 보통 |
| 타입 검사 | ❌ 없음 | ❌ 없음 | ✅ 있음 |
| JSX/TSX 지원 | ✅ | ✅ (내장 지원) | ✅ |
| 폴리필 | ✅ 플러그인 활용 | ❌ 수동 폴리필 필요 | ❌ 수동 폴리필 필요 |
| 플러그인 생태계 | 풍부 | 제한적 | 없음 (타입 기반 도구 위주) |
🧩 3. es-shims
es-shims는 ES 스펙을 구현한 폴리필 세트로, core-js의 대안 중 하나입니다.
🌐 특징
- 각 ES 기능별로 모듈화되어 있어 필요한 기능만 개별적으로 불러올 수 있음
Array.prototype.includes,Object.assign,Promise등 구체적인 API 지원- CommonJS / ESM 모두 지원
- 정확한 스펙 구현과 테스트를 기반으로 신뢰성 높음
- 레거시 브라우저 최적화에 유리
예시
npm install es-shim-unscopables --save
require('array.prototype.includes/auto'); // 자동 설치
🆚 core-js vs es-shims
| 항목 | core-js |
es-shims |
|---|---|---|
| 사용 방식 | 프리셋/플러그인 또는 직접 import | 각 기능별 모듈 수동 로딩 |
| 업데이트 주기 | 활발함 | 활발함 |
| 번들 크기 | 크고 무겁지만 자동화됨 | 작지만 수동 관리 필요 |
| 글로벌 오염 | useBuiltIns 사용 시 최소화 가능 |
대부분 safe-shim 처리됨 |
| 스펙 정밀도 | 좋음 | 매우 정밀 |
☁️ 4. polyfill.io
polyfill.io는 필요한 폴리필을 자동으로 로딩해주는 CDN 서비스입니다. 사용자의 User-Agent를 감지하여 필요한 폴리필만 제공하는 스마트 폴리필 서비스입니다.
✅ 사용 방법
<script src="https://polyfill.io/v3/polyfill.min.js?features=default,Array.prototype.includes"></script>
?features=...로 필요한 기능 지정- UA 기반으로 브라우저별로 최적화된 폴리필만 전달됨
- HTTP/2를 사용한 빠른 전송
⚠️ 공급망 공격 이슈 (2024년)
2024년 6월, polyfill.io의 소유권이 이전되면서 공급망 공격 가능성이 대두됨:
- 신뢰성 문제 발생
- 주요 OSS 단체들(Next.js, Svelte 등)이 사용 중단 권고
✅ 대안:
- 직접 호스팅:
polyfill-library패키지 사용 후 자체 배포
npm install polyfill-library
const polyfill = require('polyfill-library');
const fs = require('fs');
polyfill.getPolyfillString({ uaString: '...', features: { default: {} } })
.then(output => fs.writeFileSync('polyfill.js', output));
🧾 전체 비교 요약
| 도구 | 유형 | 트랜스파일 | 타입 검사 | 폴리필 지원 | 특징 요약 |
|---|---|---|---|---|---|
| Babel + core-js | 트랜스파일러 + 폴리필 | ✅ | ❌ | ✅ (자동/선택적) | 가장 유연하고 생태계 풍부, 느릴 수 있음 |
| TypeScript Compiler | 트랜스파일러 | ✅ | ✅ | ❌ (수동) | 타입 검사 가능, 빌드 느림, 폴리필 없음 |
| SWC | 초고속 트랜스파일러 | ✅ | ❌ | ❌ (수동) | 매우 빠름, 바벨 대체 가능, 설정 간단 |
| es-shims | 폴리필 | ❌ | ❌ | ✅ (수동) | 기능 단위 폴리필, 모듈화, 레거시 호환성 탁월 |
| polyfill.io | 폴리필 CDN | ❌ | ❌ | ✅ (자동) | UA 기반 자동 최적화, 최근에는 보안 이슈로 사용 지양 |
✅ 결론: 상황별 추천
| 상황 | 추천 도구 |
|---|---|
| 빠른 빌드, React/TS 프로젝트 | SWC + es-shims 수동 폴리필 |
| 레거시 브라우저 완벽 지원이 필요할 때 | Babel + core-js@3 |
| 빠르게 POC나 데모를 만들고 싶을 때 | Babel or TypeScript only |
| 타입 검사 + 트랜스파일이 필요하지만 폴리필은 직접 관리 | TypeScript Compiler + es-shims |
| CDN으로 간단히 처리하고 싶지만 신뢰성 이슈 걱정된다면 | 직접 빌드한 polyfill-library |