Babel 8 Beta — CJS를 버리고 ESM 전용으로 간다

8 min read
BabelESMJavaScript빌드 도구마이그레이션
Babel 8 Beta — CJS를 버리고 ESM 전용으로 간다

Babel은 JavaScript 생태계에서 가장 오래 살아남은 도구 중 하나다. 2014년 첫 릴리스 이후 ES6→ES5 트랜스파일링의 대명사였고, JSX, TypeScript, 데코레이터 등 브라우저가 지원하기 전의 문법을 먼저 써볼 수 있게 해준 "시간 여행 머신" 같은 존재다. 그 Babel이 8.0 베타에 진입했다. 핵심 변화는 ESM(ES Modules) 전용 배포다. CommonJS는 더 이상 제공하지 않는다.


Babel 7에서 무엇이 달라지나

항목Babel 7Babel 8
모듈 시스템CJS + ESM 이중 배포ESM 전용
Node.js 최소 버전12+20+
설정 파일.babelrc, babel.config.jsbabel.config.mjs 권장
기술 부채10년치 누적대량 정리
단계마지막 버전 7.29.0 배포 완료베타 (안정화 중)

가장 눈에 띄는 변화는 역시 CJS 드롭이다. 비유하면 양손잡이 투수가 "이제 왼손으로만 던지겠다"고 선언한 것과 같다. 한쪽을 포기하는 대신, 남은 쪽에 집중해서 더 빠르고 가벼운 패키지를 만들겠다는 판단이다.

[💡 잠깐! 이 용어는?] ESM(ES Modules): import/export 구문을 사용하는 JavaScript 표준 모듈 시스템. Node.js 12부터 실험적으로, 20부터는 require(esm)까지 지원하며 사실상 표준이 됐다.


ESM 전용이 가능해진 배경

Babel 8이 CJS를 버릴 수 있게 된 결정적 이유는 Node.js 20의 require(esm) 지원이다. 이전에는 ESM 전용 패키지를 require()로 불러올 수 없어서, CJS 사용자를 위해 이중 배포가 필수였다. Node.js 20이 이 제약을 제거하면서 ESM 전용 배포의 현실적 장벽이 사라졌다.

babel.config.mjs — CJS에서 ESM require 예시
// Node.js 20+에서는 CJS에서 ESM을 require() 가능
// 이전에는 이 코드가 에러를 던졌다
const babel = require('@babel/core')
 
// babel.config.mjs (ESM 설정 파일)
export default {
  presets: [
    ['@babel/preset-env', { targets: { node: 'current' } }],
    '@babel/preset-typescript'
  ]
}

Babel 팀은 블로그에서 "계획했던 모든 브레이킹 체인지를 완료했고, 대량의 기술 부채를 제거했다"고 밝혔다. 2년 넘게 알파를 유지한 이유가 여기에 있다. 내부 코드를 대대적으로 정리하면서 동시에 Babel 7과 8을 컴파일 타임 피처 플래그로 하나의 코드베이스에서 관리해왔다.


마이그레이션 전략

Babel 팀은 대부분의 브레이킹 체인지를 Babel 7에서 옵션으로 미리 제공해왔다. 덕분에 Babel 7 최신 버전에서 옵션을 하나씩 켜면서 점진적으로 전환할 수 있다.

1단계: Babel 7을 최신 버전(7.29.0)으로 업데이트

babel 7 최신화
npm install @babel/core@7 @babel/preset-env@7 @babel/preset-typescript@7

2단계: Babel 8 호환 옵션 활성화

babel.config.js — 호환 옵션 켜기
module.exports = {
  presets: [
    ['@babel/preset-env', {
      targets: { node: '20' },
      // Babel 8에서 기본값이 되는 옵션들을 미리 활성화
      bugfixes: true
    }],
    ['@babel/preset-typescript', {
      // Babel 8에서 기본값이 되는 옵션
      allowDeclareFields: true
    }]
  ]
}

3단계: 설정 파일을 ESM으로 전환

babel.config.mjs
export default {
  presets: [
    ['@babel/preset-env', {
      targets: { node: '20' },
      bugfixes: true
    }],
    '@babel/preset-typescript'
  ]
}

.babelrcbabel.config.js(CJS) 대신 babel.config.mjs(ESM)로 전환하면 Babel 8에서도 그대로 사용할 수 있다.

[💡 잠깐! 이 용어는?] 피처 플래그(Feature Flag): 코드에 새 기능을 넣어두되, 런타임이나 빌드 타임에 켜고 끌 수 있게 하는 기법. Babel은 7과 8 코드를 하나의 리포지토리에서 관리하면서 피처 플래그로 분기했다.


Babel이 아직도 필요한가?

TypeScript가 자체 트랜스파일링을 하고, tsc --noEmit으로 타입 체크만 쓰는 프로젝트도 많다. Vite는 esbuild를, Next.js는 SWC를 기본 트랜스파일러로 쓴다. 그렇다면 Babel의 자리는 어디인가?

시나리오Babel 필요 여부
Next.js/Vite 기본 설정불필요 (SWC/esbuild가 대체)
커스텀 Babel 플러그인 사용필요
TC39 Stage 2~3 제안 사용필요 (데코레이터, using 등)
레거시 프로젝트 유지보수필요
Relay, Styled Components 등 Babel 의존 도구필요

Babel의 핵심 가치는 플러그인 생태계다. 코드 변환의 "스위스 아미 나이프"로서, SWC나 esbuild가 커버하지 못하는 커스텀 트랜스폼이 필요한 프로젝트에서는 여전히 대체재가 없다.


Babel 7.29.0 — 마지막 메이저 릴리스

Babel 7의 마지막 릴리스인 7.29.0도 함께 배포됐다. 이 버전은 사실상 "Babel 7의 유종의 미"다. 이후 Babel 7에는 보안 패치만 제공될 예정이며, 모든 개발 역량은 Babel 8에 집중된다.

비유하면 건물의 한 층에서 다음 층으로 이사하면서, 이전 층의 조명과 수도는 유지하되 새 가구는 더 이상 들이지 않겠다는 것이다.


정리

  • Babel 8 Beta가 2년간의 알파를 거쳐 드디어 출시됐다
  • ESM 전용 배포가 가장 큰 변화다. CommonJS 번들은 더 이상 제공하지 않는다
  • Node.js 20의 require(esm) 지원이 이 전환을 가능하게 했다
  • 마이그레이션은 Babel 7에서 호환 옵션을 미리 켜는 점진적 전략이 권장된다
  • Babel 7.29.0이 7.x의 마지막 릴리스이며, 이후는 보안 패치만 제공된다
  • SWC/esbuild 시대에도 커스텀 플러그인이 필요한 프로젝트에서 Babel은 여전히 유효하다

Babel 8을 당장 도입할 필요는 없다. 하지만 Babel 7을 사용 중이라면 7.29.0으로 업데이트하고, 호환 옵션을 켜두는 것부터 시작하는 게 좋다. 이 작업만 해두면 Babel 8 Stable이 나왔을 때 전환 비용이 크게 줄어든다.


참고:

관심 있을 만한 포스트

Babel 7.29.0 — 10년 역사의 마지막 마이너, 그리고 8 RC1

2026년 1월 31일, Babel 7의 마지막 마이너 릴리스가 공개됐다. 이 버전이 갖는 역사적 의미와 Babel 8 RC1의 핵심 변화를 정리한다.

BabelJavaScript

Native JSON Modules — 번들러 없이 JSON을 import하는 시대

Import Attributes와 함께 표준이 된 native JSON module. 어떻게 동작하고, 기존 번들러 방식과 뭐가 다른지 정리했다.

JavaScriptESM

jQuery 4.0 — 10년 만의 메이저 릴리스, 무엇이 바뀌었나

jQuery가 20주년을 맞아 10년 만에 메이저 버전을 출시했다. IE 지원 축소, ES 모듈 전환, Trusted Types 등 핵심 변경 사항을 정리한다.

jQueryJavaScript

Error.isError() — realm을 넘나드는 안전한 에러 검사 API

instanceof Error가 iframe과 worker에서 실패하는 이유, 그리고 이를 근본적으로 해결하는 Error.isError()의 동작 원리를 정리한다.

JavaScriptError.isError

V8의 Sea of Nodes 탈출기 — 왜 우아한 이론이 실전에서 무너졌는가

V8 팀이 10년간 사용한 Sea of Nodes IR을 포기하고 Turboshaft로 전환한 7가지 이유와 그 교훈을 정리한다.

V8컴파일러

배포 전 출국 심사 — Publint로 npm 패키지 실수를 원천 차단하는 법

npm 패키지의 exports, entry points, 모듈 포맷을 배포 전에 검증하는 Publint 도구 사용법.

npmPublint

달리는 차의 엔진을 바꾸다 — Nx 모노레포에서 Bun 도입까지의 여정

Nx 18에서 21까지 버전 업그레이드와 Bun 패키지 매니저 도입을 동시에 진행한 컬리의 마이그레이션 전략과 실전 이슈를 정리한다.

NxBun

V8 Mutable Heap Numbers — 숫자 하나 바꿀 때마다 새 객체를 만들던 비효율을 잡다

V8 엔진이 스크립트 컨텍스트의 숫자 변수를 매번 새 HeapNumber로 할당하던 방식을 제자리 수정(mutable)으로 바꿔 최대 2.5배 성능 향상을 달성했다.

V8JavaScript