본문 바로가기
JavaScript

Babel 이해하기

by Vintz 2023. 3. 6.
반응형

이름이 왜 바벨(Babel)일까?

바벨(babel)의 원래 이름은 '6to5'였다. 바벨을 만든 세바스찬 맥켄지(Sebastian McKenzie)는 호주의 고등학교 마지막 학년에 6to5를 만들기 시작했고, 그 당시 만든 계기가 정확히 기억이 나지 않는다고 했다. ES6를 우연히 만났고, 그 후 어찌저찌 'estraverse'라는 파서 라이브러리를 발견해서 더 깊이 배우기로 결심하고, 오픈 소스에 대한 환상이 떠오른 것 같다고 말한다.

 

'6to5'라는 이름에서 알 수 있듯이 미래에 사용(future-proof)하기 적합한 이름은 아니었다. 많은 사용자들은 이것이 단지 ES6가 지원되기 전까지의 '임시 해결책'이라고 생각했지만, 맥켄지는 이것이 미래가 보장될 뿐만 아니라 잠재적으로 미래의 표준에 영향을 미치는, 확장할 수 있는 것들이 있다고 믿었다.

 

따라서 미래 지향적인 관점에서 바벨 팀은 6to5라는 이름이 프로젝트 목표에 대한 올바른 메시지를 전달하지 못한다고 생각했다. 그래서 결국 이름을 'Babel'로 변경한 것이다.

 

팀과 커뮤니티에서 긴 토론과 논의 끝에 결정된 것인데, 더글러스 애덤스(Douglas Adams)의 은하수를 여행하는 히치하이커를 위한 안내서에서 나오는 다른 언어를 자동으로 번역하는 물고기인 바벨피쉬에 고개를 끄덕이며 바벨에 동의했다고 한다.

바벨이 해결해 주는 문제

크롬, 엣지, 사파리, 인터넷 익스플로러 등의 브라우저들은 시기별로 지원하는 ES 문법들이 서로 다르다. 다른 환경, 어떤 요인으로 인해 일부만 채택하여 지원하거나 1~2년 뒤에 지원하는 경우이다.

 

예를 들어, 2015년 6월에 발표한 ES6의 브라우저별 지원 시기는 다음과 같다.(명세의 최소 95% 지원 기준)

  • 크롬 - 2016년 05월
  • 오페라 - 2016년 06월
  • 사파리 - 2016년 09월
  • 엣지 - 2017년 04월
  • 파이어폭스 - 2017년 06월

이렇게 브라우저별로 지원하는 시기가 달라, 특정 브라우저에서만 기능이 동작한다면 크로스 브라우징 이슈와 함께 코드의 일관성을 해치고, 불안하게 하며 최신 자바스크립트 문법을 사용하기 꺼려지게 만든다. 가령 프로젝트에 IE11 지원을 제외하는 것으로 결정이 나서 신나게 ES6+ 문법으로 개발했는데 막바지에 지원하는 것으로 변경되었다면? 벌써부터 머리가 아플 것이다.(IE11은 const, let, Map, Set 등 ES6의 약 10% 정도만 지원을 한다.)

 

오래된 브라우저나 Windows 8.1, Windows 7 사용자가 IE11을 아직 사용할 수도 있다.(Windows 10에서는 IE11이 비활성화 되었다.) 사용자가 매우 적다고 해도 고려해야 할 대상이다. 제공하는 서비스에 따라 범위를 좁힐 수도 있지만, 포함해야 한다면 ES6+ 문법은 사용할 수 없는 것일까?

 

자바스크립트는 매번 최신 버전을 발표하며, 브라우저가 빠르게 대응한다고 해도 사용자가 최신 브라우저를 사용할 것이라는 보장이 없다. 이러한 브라우저 호환성에 대한 문제들을 해결해 줄 수 있는 게 바로 바벨이다.

자바스크립트 컴파일러 바벨

바벨은 주로 ES6+ 코드를 이전 버전과 호환되는 코드로 변환하는 데 사용된다. 따라서 위에서 말한 브라우저 호환성에 대한 걱정 없이 최신 자바스크립트 문법을 사용할 수 있다.

 

하지만 기존에 없던 메서드(Array.prototype.includes ・・・)나 새로운 객체(Promise, IntersectionObserver ・・・), 새로운 함수(fetch)를 만들어줄 수는 없다.

 

해당 문제는 충전솜이라는 뜻을 가진 폴리필(polyfill)을 추가하여 해결 할 수 있다. 바벨 폴리필을 추가하면, 충전솜처럼 부족한 부분들을 채울 수 있다. 즉, 폴리필은 기본적으로 지원하지 않는 최신 기능을 구형 브라우저에서 사용할 수 있게 만드는 코드 뭉치이다.

 

따라서 새로운 객체/메서드/함수는 폴리필로, 구문 변환은 바벨이 컴파일/트랜스파일하여 브라우저 호환성을 해결한다.

트랜스파일러? 컴파일러?

바벨 공식 문서에서 가장 먼저 보이는 문구는 "바벨은 자바스크립트 컴파일러입니다."이다. 내 얕은 지식으로 봤을 땐 '왜 컴파일러라고 하지? 트랜스파일러가 아닌가?'라는 생각이 들었다. 짧은 시간 동안 해당 내용에 대해 찾아봤다. 일반적으로 컴파일러는 고수준의 언어에서 저수준의 언어로 변환, 트랜스파일러는 비슷한 수준의 추상화를 가진 언어 간의 변환을 뜻한다. 트랜스파일러는 '소스 대 소스 컴파일러(source-to-source compiler)'라고도 불리며 컴파일러의 일종이라고 설명되어 있다. 엄밀히 말하면 트랜스파일러로 볼 수 있지만, 더 큰 범주에선 컴파일러인 것이다. 개인적으로 트랜스파일러와 컴파일러라고 구별하여 부르는 것이 의사소통에 있어서 어느 정도 가치 있다고 생각을 하지만, 공식 문서에서 컴파일러라고 설명하는 만큼 해당 문서의 고유한 단어로 사용하는 것이 맞다고 생각한다.

"바벨은 무엇일까요? 바벨은 자바스크립트 컴파일러입니다. C 컴파일러처럼 어셈블리어나 바이너리 코드를 생성하는 전통적인 컴파일러는 아닙니다. 바벨은 자바스크립트를 자바스크립트로 컴파일하는 컴파일러(javascript to javascript compiler)입니다. 기존의 코드와 같은 의미를 갖는 또 다른 자바스크립트 코드를 출력합니다." - 바벨 팀원인 니콜라스 리바우도(Nicolò Ribaudo)의 @babel/how-to 발표 중 내용

바벨 시작하기

바벨을 사용하려면 일반적으로 설치 해야 할 3개의 패키지가 있다.

npm i -D @babel/core @babel/cli @babel/preset-env

@babel/core

@babel/core 패키지에는 바벨이 작동하기 위한 핵심 기능들이 들어있다. 바벨이 작동하는 방식은 여러 단계로 나뉘는데, 먼저 입력 소스 코드를 가져와 파싱하여 AST(Abstract Syntax Tree)로 생성한 후, 이 AST를 변환하고, 출력 소스 코드를 생성해 나타낸다.

@babel/parser, @babel/traverse, @babel/generator 이렇게 세 개의 API를 제공한다.

@babel/cli

@babel/cli 패키지는 터미널의 명령어 인터페이스를 통해 파일을 컴파일 할 수 있도록 한다. 공식 문서에서는 관리의 용이성을 이유로 프로젝트별로 로컬에 설치하는 것을 권장하고 있다.

사용법

npx babel [파일이름]을 입력하면 컴파일을 한다.

npx babel script.js

script.js 파일을 컴파일하고, 표준출력(stdout)으로 출력한다. 참고로, @babel/cil@babel/core를 설치하기 전에 npx babel부터 실행하면 npx가 오래된 버전인 babel 6.x를 설치하기 때문에 주의하자.

npx babel script.js
# output...

파일로 출력하고 싶다면 --out-file 또는 -o를 사용할 수 있다.

npx babel script.js --out-file script-compiled.js

파일에 변경 사항이 생길 때마다 파일을 컴파일하려면 --watch 또는 -w 옵션을 사용한다.

npx babel script.js --watch --out-file script-compiled.js
# The watcher is ready.

이 외의 다양한 옵션들은 공식 문서를 참고하자.

@babel/preset-env

@babel/core는 바벨 작동의 핵심 기능을, @babel/cli는 바벨의 Command Line Interface(CLI) 기능을 제공해준다는 것을 알게 되었다. 그렇다면 이 @babel/preset-env(이하 preset-env)는 무엇일까?

 

preset-env는 환경에 대한 구문 변환 설정을 쉽게 설정하고 관리할 수 있게 해준다. 바벨에선 모든 것이 플러그인이다. 즉, 플러그인 없이는 아무 것도 하지 않고 입력 코드를 가져와 파싱하고, 다시 출력만 하며, 어쩌다 코드 포맷팅을 해줄 뿐이다.

 

사전에 구문 변환을 위한 여러 플러그인들을 설치해야 하는데, preset-env는 이런 수고를 덜어준다. preset-env라는 이름과 같이 사전 설정 환경(플러그인 모음)인 것이다. 바벨은 preset-env 외에도 리액트나 타입스크립트 등을 위한 preset을 제공하고 있다.

 

따라서 내 환경에 맞게 추가로 플러그인 설치를 하거나, preset-env 없이 필요한 플러그인만 설치해도 된다. 예를 들어, ES6의 화살표 함수를 ES5 구문으로 변환하고 싶다면 @babel/plugin-transform-arrow-functions 플러그인을 설치하면 된다.(@babel/core@babel/cli가 설치되어 있다고 가정)

npm i -D @babel/plugin-transform-arrow-functions

플러그인을 실행하여 src 폴더에 있는 모든 자바스크립트 파일을 파싱하고, 각 파일을 lib 폴더에 출력하도록 해보자.

./node_modules/.bin/babel src --out-dir lib --plugins=@babel/plugin-transform-arrow-functions

그럼 다음과 같이 변환이 될 것이다.

const fn = () => 1;

// 다음으로 변환

const fn = function () {
  return 1;
};

이렇게 본인의 환경에 맞게 플러그인을 구성할 수도 있다. 플러그인 목록도 역시 공식 문서를 참고하자.

바벨 설정

필요에 따라 설정 파일을 사용하는 여러 방법들이 있다. 지금은 프로젝트 루트(package.json이 있는 위치)에 babel.config.json을 생성해서, 몇 가지 옵션들을 살펴보자.

babel.config.json

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "edge": "17",
          "firefox": "60",
          "chrome": "67",
          "safari": "11.1"
        }
      }
    ]
  ]
}

preset-env의 대상 브라우저(targets)를 2018년 이후의 버전들로 구성했기 때문에, 해당 설정은 화살표 함수와 같은 ES6 구문들을 그대로 출력한다.(IE11을 제외하고 2016~2017년 사이에 대표적인 브라우저들은 모두 ES6를 지원했다.) 만약 대상 브라우저에 IE11을 추가한다면 ES5 문법으로 변환하여 출력한다.

 

preset-env는 대상 브라우저에서 사용할 수 없는 기능에 대한 변환 플러그인만 로드한다.

 

따라서 만약 다음과 같이 타겟을 지정하지 않을 경우, 바벨은 가장 오래된 브라우저를 대상으로 가정한다.

{
  "presets": ["@babel/preset-env"]
}

결국 @babel/preset-env는 모든 ES2015-ES2022 코드를 ES5와 호환되도록 변환한다. 해당 설정은 출력 코드의 크기를 줄이기 위해 바벨에서 권장하지 않고 있다. 따라서 브라우저 대상 범위를 환경에 맞게 지정해 주는 것이 좋다.

폴리필

바벨 7.4.0부터 @babel/polyfill 패키지는 더 이상 사용되지 않는다. core-js@3가 나오면서, 폴리필을 위한 core-js 버전을 직접 import하기 위해서다. 

// before
import "@babel/polyfill";

// after 원하는 버전을 로드하고, 두 패키지를 독립적으로 업데이트 할 수 있게 됐다.
import "core-js/stable"; // 2와 3 중에 선택
import "regenerator-runtime/runtime"; // 필요한 경우

이렇게 폴리필이 필요한 경우, core-js를 직접 import 할 수 있다. 또 다른 옵션으로는 @babel/preset-envuseBuiltIns 옵션을 사용하는 것이다. 이 옵션은 preset-env가 폴리필을 처리하는 방법을 구성하며, 다음 세 가지가 있다.

"useBuiltIns": "usage"

각 파일에서 실제 사용한 폴리필만 import를 추가한다.

In

// a.js
var a = new Promise();

// b.js
var b = [1, 2, 3, 4, 5].includes(5);

Out(대상 환경이 지원하지 않는 경우)

// a.js
import "core-js/modules/es.promise.js";
var a = new Promise();

// b.js
import "core-js/modules/es.array.includes.js";
var b = [1, 2, 3, 4, 5].includes(5);

대상 환경이 지원하는 기능이라면 import 하지 않는다.

"useBuiltIns": "entry"

core-js 패키지의 삽입문(import)을 대상 브라우저에 필요한 개별 패키지 삽입문(import)으로 출력한다. @babel/polyfill을 사용하는 경우, 이미 core-js가 포함되어 두 번 가져오기 때문에 오류가 발생한다. 또한 전역 충돌 및 추적하기 어려운 기타 문제가 발생할 수 있기 때문에 하나의 진입점(entry)만을 갖도록 앱 전체에 import "core-js";를 단 한 번만 사용하는 것을 권장한다.

 

"core-js"를 가져오면 가능한 모든 ECMAScript 기능에 대한 폴리필을 가져오기 때문에 일부만 필요한 경우엔 다음과 같이 할 수 있다.

In

import 'core-js/es/array';
import 'core-js/proposals/math-extensions';

Out (대상 환경에 따라 다르게 출력)

import "core-js/modules/es.array.unscopables.flat";
import "core-js/modules/es.array.unscopables.flat-map";
import "core-js/modules/esnext.math.clamp";
import "core-js/modules/esnext.math.deg-per-rad";
import "core-js/modules/esnext.math.degrees";
import "core-js/modules/esnext.math.fscale";
import "core-js/modules/esnext.math.rad-per-deg";
import "core-js/modules/esnext.math.radians";
import "core-js/modules/esnext.math.scale";

더 많은 진입점은 core-js 문서를 확인하자.

"useBuiltIns": false

기본값, 폴리필을 사용하지 않음

 

마지막으로, "corejs" 옵션을 통해 core-js 모듈의 버전을 설정해준다. 이 옵션은 "useBuiltIns" 옵션과 함께 사용해야만 효과가 있다. 기본값은 "2.0"이며, 현재 2버전은 지원이 종료되었기 때문에 3버전 이상을 지정해주자.

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "edge": "17",
          "firefox": "60",
          "chrome": "67",
          "safari": "11.1"
        },
        "useBuiltIns": "usage",
        "corejs": "3.8"
      }
    ]
  ]
}

(v7.8.0 이상이 필요 하므로 더 오래된 버전은 babel.config.js 부분 참고)

바벨로는 부족해

바벨은 새로운 ES 문법을 지원하지 않는 브라우저에서도 사용할 수 있게 해주는 '변환' 기능을 제공하는 도구이다. 즉, 자바스크립트 컴파일러 역할만을 수행하기 때문에 웹팩(Webpack)과 같은 모듈 번들러가 하는 일을 처리하지 못한다.

 

이는 ES 모듈의 모듈 불러오기, 내보내기 기능을 사용한다면 바벨만으로는 웹 브라우저 환경에서 사용할 수 없다는 것을 뜻한다. 따라서 Webpack과 같은 모듈 번들러를 사용해 빌드 시스템을 구축한 후, 바벨 컴파일러를 로더로 추가해 사용해야 한다.

 

참고: Webpack 러닝 가이드 - Babel CLI 구성

요약

  • 브라우저마다 이해하는 언어가 달라서 개발하는 데 어려움이 있었다.
  • 그 당시 최신 문법이었던 ES6를 ES5로 파싱하는 라이브러리인 '6to5'가 나왔다.
  • 맥켄지는 '6to5'보다 더 미래 지향적인 목표를 갖고 있었다. 따라서 이름을 바꾸기로 결정 하였고, '다른 언어를 자동으로 번역하는 물고기인 바벨피쉬'에서 이름을 따와 바벨(babel)로 짓게 되었다.
  • 바벨은 자바스크립트 컴파일러로써 이제는 ES6뿐만 아니라 최신 문법을 사용하더라도, 모든 브라우저가 이해할 수 있도록 컴파일하여 브라우저 호환성 이슈를 해결해 준다.
  • 바벨 작동의 핵심 기능이 담긴 @babel/core, 터미널에서 바벨을 쉽게 컴파일하게 만들어 주는 @babel/cli 설치는 필수이다.
  • 바벨은 다양한 플러그인 조합으로 환경에 맞게 구성하여 변환할 수 있으며, 플러그인 모음인 @babel/preset-env를 설치하면 간편하다.
  • 구문 변환 외 구형 브라우저에 필요한 객체/메서드/함수는 바벨 폴리필을 추가해야 한다.

참고

Babel Docs
Webpack 러닝 가이드
[10분 테코톡] 나인의 Babel
Nicolò Ribaudo — @babel/how-to
Babel7과 corejs3 설정으로 전역 오염 없는 폴리필 사용하기
Sebastian McKenzie on Babel and the Road to Rome
6to5 JavaScript Transpiler Changes Name to Babel
zloirock/core-js
프론트엔드 개발환경의 이해: Babel
야후! 바벨피쉬
컴파일과 폴리필의 차이점 분석 (babel, polyfill)
반응형