1. 배경
ES6가 등장한 지 얼마 되지 않았을 때에는 대부분의 브라우저에서 지원하지 않았기 때문에 최신 문법을 사용하고 싶어도 사용할 수 없었다.
이때 바벨이 ES6 문법으로 작성된 자바스크립트 코드를 ES5로 변환해주었다.
즉, ES6 문법을 지원하지 않는 브라우저에서도 ES6 문법을 사용할 수 있게 된 것이다.
바벨이 등장함으로 브라우저 간 호환성을 지켜야 하는 크로스 브라우징 이슈를 어느 정도 해결해주었다고 본다.
시간이 지나며 단순히 ES6 to ES5의 변환뿐 아니라 리액트의 JSX 문법, 타입스크립트와 같은 정적 타입 언어, 최신 문법 등에 대한 변환을 제공하며 프론트엔드 개발에 있어 필수적인 요소로 자리 잡았다.
바벨에서 변환하는 행위를 트랜스파일링한다고 칭한다.
고수준 언어->저수준 언어의 컴파일링과 차이를 두기 위해 트랜스파일링이라고 하는 것 같다.
2. 바벨 맛보기
그럼 바벨이 트랜스파일링 한 결과를 간략하게 알아보자.
리액트의 JSX문법, 화살표 함수, 템플릿 리터럴 등을 위와 같이 변환해주고 있다.
3. @babel/cli 를 통한 실행
npm i @babel/core @babel/cli
- @babel/core: 바벨을 실행하기 위해 필요한 패키지.
- @babel/cli: 커맨드라인에서 바벨을 실행할 수 있도록 하는 패키지.
프로젝트의 루트에 위와 같은 코드를 작성해보자.
npx babel SampleBabel.jsx
위의 명령어로 바벨을 실행해보면 아마 오류가 발생할 것이다.
바벨이 JSX 문법을 인식하지 못했기 때문이다. 뿐만 아니라 우리가 작성한 화살표 함수와 템플릿 리터럴에 대한 변환은 일어나지 않았다.
바벨을 사용하면 최신 문법을 모두 사용할 수 있다고 착각하기 쉽다.
바벨을 사용하더라도 폴리필과 플러그인에 대한 적절한 설정이 필요하다.
플러그인이란? 바벨이 어떻게 코드를 변환할 지에 대한 룰을 정의해주는 요소이다.
필요/목적에 의해 모아놓은 플러그인의 집합을 프리셋(preset)이라고 한다.
@babel/preset-react
@babel/preset-env
@babel/preset-typescript
@babel/preset-flow
@babel/preset-jest
등 다양한 프리셋이 존재한다.
폴리필에 대해서는 밑에서 다루기로 하자.
아무튼 위의 코드에서 JSX 문법을 인식할 수 있도록 적절한 플러그인과 프리셋을 추가해보자.
npm i @babel/preset-react @babel/plugin-transform-arrow-functions @babel/plugin-transform-template-literals
- @babel/preset-react : 리액트 개발환경에서 필요한 플러그인들을 모아놓은 프리셋
- @babel/plugin-transform-arrow-functions : 화살표 함수 변환에 필요한 플러그인
- @babel/plugin-transform-template-literals : 템플릿 리터럴 변환에 필요한 플러그인
npx babel SampleBabel.jsx --presets=@babel/preset-react --plugins=@babel/plugin-transform-template-literals,@babel/plugin-transform-arrow-functions
위의 명령을 실행하면 콘솔에 변환 결과가 우리가 의도한 대로 출력된다.
4. 웹팩의 babel-loader 사용해 실행하기
바벨은 위와 같이 cli를 사용해서 실행할 수 도 있지만 웹팩에서 자바스크립트 파일에 대한 로더로 babel-loader을 지정함으로 사용할 수도 있다.
프리셋과 플러그인 설정/관리를 조금 더 쉽게 하기 위해 프로젝트의 루트에 설정파일(babel.config.js)을 생성해보자.
웹팩을 실행하기 위해 패키지를 추가로 설치해보자.
npm i webpack webpack-cli babel-loader
그리고 위와 같은 웹팩 설정 파일(webpack.config.js) 역시 프로젝트의 루트에 추가해보자.
npx webpack
그런 다음 웹팩을 실행시켜보면 잘 동작함을 볼 수 있다.
5. 폴리필이란 ?
위에서 언급했듯이 바벨은 ES6 이상의 문법들에 대하여 ES5 문법의 자바스크립트로 변환해준다.
- 화살표 함수는 일반 function으로 변환한다.
- 템플릿 리터럴은 문자열 concatenation으로 변환한다.
- Promise는 ?
Promise는 ES5 버전의 문법에 존재하지 않는 기능이다.
즉, 바벨은 추가적인 설정이 없다면 Promise에 대한 변환을 수행할 수 없다는 것이다.
그럼 Promise를 못쓰나 ?!
이를 가능케 하는 것이 폴리필이다.
바벨은 컴파일 타임에 코드를 트랜스파일링한다.
반면 폴리필은 런타임에 등록되지 않은 메서드나 기능을 주입해준다.
폴리필을 사용하는 방법은 크게 3가지가 있다.
- @babel/polyfill 를 import 해서 사용
- core-js 에서 필요한 폴리필만 import 해서 사용
- @babel/preset-env 프리셋 사용
1. @babel/polyfill 를 import 해서 사용
import '@babel/polyfill';
이 방법을 사용하면 이 녀석이 가지고 있는 모든 폴리필이 불러와진다.
즉, 내가 사용하지 않는 폴리필도 불러와지기 때문에 번들의 크기가 커질 수 있다는 단점이 있다.
때문에 바벨 공식문서에서는
import "core-js";
import "regenerator-runtime/runtime";
위와 같이 모듈을 import해서 사용하는 것을 권장하고 있다.
2. core-js 에서 필요한 폴리필만 import 해서 사용
import "core-js/modules/es.string.pad-start";
import "core-js/modules/es.string.pad-end";
위와 같이 필요한 폴리필만 선택적으로 import하는 방식이다.
https://github.com/zloirock/core-js
core-js 레포지토리를 참고하면 좋을 것 같다.
이렇게 하면 번들의 크기를 줄일 수 있다. 번들 크기 최적화가 필요하다면 이 방법이 좋을 것 같다.
하지만 매번 core-js 문서를 참조하며 import 하기는 번거롭고 필요한 것을 깜빡하고 누락하는 실수를 할 수 있다.
3. @babel/preset-env 프리셋 사용
SampleBabel을 다음과 같이 수정해보자.
그리고, babel.config.js 을 다음과 같이 수정해보자.
useBuiltIns 옵션은 @babel/preset-env 가 어떻게 폴리필을 핸들링할지 설정하는 옵션이다.
default는 false이나, 'entry' 혹은 'usage' 옵션을 사용하기 위해서는 반드시 @babel-preset-env 를 사용해야 한다.
core-js 의 버전 옵션은 로컬에 설치되어있는 core-js와 동일한 버전 값을 작성하면 된다.
설치되어있지 않다면 core-js 3버전을 설치한 뒤 config에도 3으로 작성해주자.
npm install --save core-js@3
yarn add core-js@3
그리고 바벨을 실행시키면 다음과 같은 결과가 나온다.
"use strict";
// ...
require("core-js/modules/es6.reflect.define-property.js");
require("core-js/modules/es6.reflect.delete-property.js");
require("core-js/modules/es6.reflect.get-prototype-of.js");
// ...
require("core-js/modules/es6.string.starts-with.js");
require("core-js/modules/web.dom.iterable.js");
require("regenerator-runtime/runtime.js");
var promise9 = Promise.resolve('9');
var obj = {
foo: 'bar',
aespa: 'winter'
};
var objValues = Object.values(obj);
var isWinterThere = objValues.includes('winter');
useBuiltIns: 'entry' 를 설정함으로써 크롬 40버전에 존재하지 않는 기능을 위한 모든 폴리필 들이 불러와진 것이다.
하지만 우리의 코드에서 실제로 필요한 폴리필들은 Promise, Object.values, includes 뿐이다.
즉, 불필요한 폴리필들이 많이 추가가 되었다는 것이다.
마지막으로, babel.config.js 에서 useBuiltIns: 'usage' 로 수정해보자.
"use strict";
require("core-js/modules/es6.object.to-string.js");
require("core-js/modules/es6.promise.js");
require("core-js/modules/es7.object.values.js");
require("core-js/modules/es6.string.includes.js");
require("core-js/modules/es7.array.includes.js");
var promise9 = Promise.resolve('9');
var obj = {
foo: 'bar',
aespa: 'winter'
};
var objValues = Object.values(obj);
var isWinterThere = objValues.includes('winter');
위와 같은 결과를 얻을 수 있다.
useBuiltIns: 'usage' 를 설정하면 실제로 코드에서 사용한 기능의 폴리필만 추가된다.
또한 상단에서 core-js을 import 할 필요도 없다.
폴리필을 주입하는 방법은 여러 가지가 있다.
번들 파일의 최적화가 중요한 프로젝트인가 를 고려하여 선택하는 방법이 있을 수 있겠다.
바벨에 대해서 알아보았다.
더 자세한 설명은 공식문서를 참조하는 것을 추천한다.