바벨은 자바스크립트 컴파일러이다.
Babel is a JavaScript compiler
Babel 공식페이지
컴파일러는 특정 프로그래밍 언어로 작성된 코드를 다른 프로그래밍 언어나 컴퓨터 프로세서가 이해할 수 있는 기계어로 변환하는 프로그램이다.
즉, 인간이 사용할 수 있는 언어로 작성된 프로그램을 전체 코드에 대한 오류를 검사하고 기계어로 된 프로그램으로 출력하는 번역기이다. 컴파일 뒤 실행파일을 생성한다.
그런데!!! 자바스크립트는 컴파일러가 아닌 인터프리터로 동작한다.
인터프리터는 특정 프로그래밍 언어로 작성된 코드를 기계어로 변환하는 프로그램이다.
인간이 사용할 수 있는 언어로 작성된 프로그램을 실시간으로 읽어 한 줄씩 번역한다. 한 줄씩 읽을 때마다 해당 줄에 오류가 있는지 확인한 후, 그 줄을 기계코드로 변환한다. 또한 실행파일을 생성하고 저장하지 않아 메모리는 적게 사용되지만 코드가 실행될 때마다 실행파일을 생성하여 compiler 보다 속도가 느리다.
바벨은 ES5+ 코드를 자바스크립의 하위 호환 버전으로 변환하여 오래된 브라우저에서 실행하도록 변환하는 컴파일러이다. (트랜스파일링: 특정 언어로 작성된 코드를 다른 언어로 변환)
모든 브라우저가 최신 문법, 기술(ES6) 을 지원하지 않기 때문에 구 기능(ES5)으로 변환하는 작업이 필요하다.
트랜스파일링은 최신의 자바스크립트 문법을 오래된 브라우저가 이해할 수 있도록 오래된 문법으로 변환해 준다.
폴리필은 오래된 브라우저에 네이티브로 지원하지 않는 사용자가 사용하는 메서드, 속성, API가 존재하지 않을 때 추가해 준다.
바벨은 최신 문법을 오래된 문법으로 변환해 주는 트랜스파일러 역할만 할 뿐 최신 함수를 사용할 수 있는 건 아니다.
폴리필은 프로그램이 처음 시작할 때, 지원하지 않는 기능들을 지원해 준다.
바벨은 컴파일 때 실행되고 폴리필은 런타임에 실행된다.
바벨은 JSX 문법을 변환한다.
JavaScript를 확장한 문법이다. (Javascript + xml)
템플릿 언어(HTML 내부에 변수 및 문법을 사용할 수 있는 언어)로도 보일 수 있지만 JavaScript 코드 내부에서 HTML 문법을 사용한 것이다.
JSX는 JavaScript 코드이지만 브라우저에서 단독으로 실행될 수 없다.
JSX를 바로 실행하면 브라우저는 JSX 문법을 이해하지 못하기 때문에 JSX 코드는 브라우저가 이해할 수 있는 JavaScript 코드로 트랜스파일링 해야 한다.
바벨로 컴파일된 JSX는 React.createElement를 호출하여 리액트 엘리먼트를 반환한다. 이러한 이유 때문에 JSX 문법을 사용하는 컴포넌트 파일에는 React를 import해줘야 한다.
import React from 'react';
const MyTest({ children }) = {
return <div>{children}</div>;
}
JSX가 트랜스파일링되어 변환된 내용은 이 페이지에서 확인할 수 있다.
바벨은 소스 코드를 분석하여 AST(Abstract Syntax Tree)로 변환한다.
AST는 소스 코드의 추상 구문 구조의 트리이다. 컴파일러에서 자료 구조로 사용되며 컴파일러의 구문 부석(parsing) 단계의 트리로 표현된 결과물이다. @babel/parser
의 parse 함수를 사용하여 AWT로 파싱 할 수 있다.
https://astexplorer.net/ 입력한 예시가 AST로 변환된 결과를 확인할 수 있다.
이전 단계에서 생성된 AST를 브라우저가 지원하는 오래된 문법으로 AST를 변경한다.
이 단계에서 plugin 또는 preset(plugin 배열)에 의해 관리되는데 이때 사용되는 plugin들은 @babel/traverse
을 사용하여 AST를 순회한다.
즉, @babel/traverse
을 사용하여 AST를 순회하며 각 AST 노드들은 브라우저가 지원하는 코드를 나타내는 새로운 노드들로 대체되고 새로운 AST로 변경된다.
바벨은 새로운 AST를 바탕으로 @babel/generator
를 통하여 새로운 코드를 생성한다.
위에서 알아본 바벨의 작동 방식을 @babel/core
@babel/cli
@babel/preset-env
를 사용하여 직접 적용하면서 알아보자.
npm install --save-dev @babel/core @babel/cli @babel/preset-env
프로젝트의 루트에 babel.config.json 파일을 생성한 후, 아래 예시 코드로 설정한다.
아래 코드는 Babel 공식 홈페이지를 참고하였습니다.
// ./src/babel.config.json
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
},
"useBuiltIns": "usage",
"corejs": "3.6.5"
}
]
]
}
아래 코드를 package.json에 적용한 후, npm run compile을 터미널에 입력한다면 src 폴더에 있는 모든 JavaScript 파일을 구문 분석(parse) 하고 우리가 설정한 변환을 적용하여 각 파일을 dist 폴더로 출력한다.
// package.json
{
...
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"compile": "babel src -d dist"
},
...
}
├── dist/ 컴파일된 소스 코드
│ └── index.js
└── src/ 원본 소스 코드
└── index.js
하지만 이렇게 dist로 출력된 코드는 여전히 원본 코드와 동일하다. 아직 어떤 변환을 적용하라는 설정을 하지 않았기 때문이다.
plugin은 바벨에게 코드 변환을 어떻게 수행할지 방법을 지시하는 자바스크립트 프로그램이다.
만약 ES2015+ 구문을 ES5로 변환하고자 한다면 @babel/plugin-transform-arrow-functions 플러그인을 사용하여 적용할 수 있다.
npm install --save-dev @babel/plugin-transform-arrow-functions
// package.json
{
...
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"compile": "babel src -d dist --plugins=@babel/plugin-transform-arrow-functions"
},
...
}
npm run compile
명령어를 입력하게 되면 src 내의 모든 자바스크립트 코드의 모든 화살표 함수는 ES5에 호환되는 함수 식으로 변환된다.
// ./src/index.js
const fn = () => {
console.log('test');
};
// ./dist/index.js
"use strict";
const fn = function fn() {
console.log('test');
};
preset
은 미리 정해진 플러그인 집합이다. preset을 사용하면 코드에 존재하는 다른 ES2015+ 기능에 대한 플러그인을 하나씩 추가하지 않아도 된다.
// package.json
{
...
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"compile": "babel src -d dist --presets=@babel/env"
},
...
}
npm run compile
명령어를 입력하게 되면 src 내의 모든 자바스크립트 코드의 모던 자바스크립트 구문은 ES5에 호환되는 구문으로 변환된다.
// ./src/index.js
const test = 'test';
const fn = () => {
console.log('test');
};
// ./dist/index.js
"use strict";
var test = 'test';
var fn = function fn() {
console.log('test');
};
preset은 옵션을 사용할 수 있는데 명령어를 통해 cli 옵션과 preset 옵션을 모두 전달하는 대신 설정 파일을 사용하여 옵션을 전달할 수 있다.
프로젝트 root 위치에 babel.config.json 파일을 생성한 후, 아래 코드를 설정한다.
env preset은 targets에 설정한 브라우저에서 사용할 수 없는 기능에 대한 변환 플러그인만 로드한다.
// babel.config.json
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
}
}
]
]
}
폴리필을 사용하는 방법은 세 가지가 존재한다.
@babel/polyfill
모듈 사용하기npm install --save @babel/polyfill
--save
옵션을 사용하는 이유는 폴리필은 런타임에 필요한 기능을 주입하기 때문이다.@babel/polyfill
은 더 이상 사용되지 않고 있다. core-js
에서 필요한 폴리필 직접 import 하여 사용하기npm install core-js
필요한 폴리필을 코드 상단에 import하여 사용한다.import 'core-js/actual/array/from';
import 'core-js/actual/array/group';
import 'core-js/actual/set';
import 'core-js/actual/promise';
import 'core-js/actual/structured-clone';
import 'core-js/actual/queue-microtask';
core-js에서 필요한 폴리필을 사용하기 위해서는 어떤 폴리필이 필요한지 알고 있어야 한다. 또 직접 import 해야 하기 때문에 실수가 발생할 수 있다.@babel/preset-env
에서 제공하는 옵션 사용하기@babel/preset-env
에서 제공하는 옵션 사용하여 폴리필 적용하기"useBuiltIns": "usage"
을 설정한다.
usage로 설정하면 target 대상 환경에 누락된 기능이 있는지 모든 코드를 검사한 후 필요한 폴리필만 포함한다.
// babel.config.json
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
},
"useBuiltIns": "usage"
}
]
]
}
babel.config.* 파일은 .json, .js, .tftps, .tftps 등의 확장자를 가진다.
프로젝트 전체 구성을 위해 바벨은 root 폴더에서 babel.config. 파일을 검색한다.
configFile(: string | boolean) 값을 사용하면 기본 구성 파일(babel.config.) 검색 동작을 재정의할 수 있다. configFile을 false로 설정하면 프로젝트 전체 구성을 비활성 할 수 있다.
.babelrc.* 파일은 .json, .js, .cjs, .mjs 확장자를 가진다.
.babelrc 파일은 확장자가 없다.
package.son 파일은 babel 키를 사용한다.
바벨은 컴파일 중인 'filename'에서 시작하는 디렉터리 구조를 검색하여 지원되는 확장자를 사용하여 프로젝트의 단일 부분에만 적용되는 구성을 로드한다. 프로젝트 내 서드파티 라이브러리가 바벨에 의해 변환되지 않기를 원할 때 사용할 수 있다.
상대 파일 구성은 기본 구성 파일보다 더 높은 우선순위로 구분되며 검색된다.
상대 파일 구성은 프로젝트 전체 구성 위에 병합되기 때문에 전체 구성을 재정의하여 사용할 수 있다.
.js 와 .json 외에도 다른 확장자를 사용할 수 있지만 아마 이 두 가지 확장자를 많이 사용할 것이다.
그렇다면 이 두 개 중 어떤 확장자를 사용하면 좋을까?
.js 구성 파일은 구성 함수(Config Function API)를 전달할 함수를 export 할 수 있기 때문에 바벨 구성(config) api가 노출될 수 있다.
api 객체는 구성 파일(config-file) 특정 API와 함께 바벨 자체가 인덱스 모듈에서 노출하는 모든 것을 노출한다.
// .js 구성 파일
module.exports = function(api) {
return {};
};
.js 구성은 캐싱이 어려워진다. 바벨은 컴파일될 때마다 구성 내에서 참조되는 플러그인과 preset 함수들을 다시 실행하는 것 때문에 구성 함수를 다시 실행하는 것을 피하려 한다. 따라서 .js 구성 파일을 사용하면 구성 함수 API에 대한 캐시 설정을 따로 해줘야 하기 때문에 복잡도가 높아진다.
.js 구성파일 사용 시, 캐시에 대한 부분은 제가 이해한 부분을 설명한 것 입니다. 혹시 잘못된 부분이 있다면 댓글 남겨주세요.
Config Function API
위와 같은 .js 구성 파일 사용 시, 따라오는 단점 때문에 .json 확장자를 사용하는 것이 더 선호된다.
NextJs는 next/babel prset을 포함하고 있다. 이 preset에는 React 어플리케이션과 서버사이드 코드를 컴파일하는데 필요한 모든 것이 포함되어 있다.
Next.js includes the next/babel preset to your app, which includes everything needed to compile React applications and server-side code.
NextJs 공식홈페이지
NextJs에서 제공하는 기본 바벨 구성을 프로젝트의 root에 .babelrc. 또는 babel.config. 파일을 생성하여 확장할 수도 있다. 이때 구성 파일에 preset에는 next/babel preset을 꼭 설정해 줘야 한다.
preset-env
의 모듈 옵션은 false로 유지해야한다. 그렇지 않으면 웹 팩 코드 분할이 해제된다.
{
"presets": ["next/babel", {
"preset-env": false
}],
"plugins": [],
}
이후, 원하는 preset이나 plugin을 배열 안에 추가하면 된다.
NextJS 컴파일러
NextJS는 12부터 SWC를 사용하여 Rust로 작성된 컴파일러를 기본적으로 활성화한다. NextJS 컴파일러를 사용하면 바벨보다 더 빠르게 컴파일 할 수 있다. 하지만 기존 바벨 구성이 있거나 NextJs 컴파일러가 지원하지 않는 기능을 사용하고 있는 경우 바벨을 계속 사용해야 한다.
이번 글을 작성하며 얼마나 바벨에 대해 무지했는지 알 수 있는 시간이었다.
심지어 내가 진행하고 있는 개인 프로젝트(NextJs 사용) root에 .babelrc.js와 babel.config.json이 동시에 존재하고 있어 수정했다...
아직 바벨에 대한 모든 이해를 한 것은 아니기 때문에 이 글은 계속해서 수정할 예정이다.
https://babeljs.io/docs/en/
https://bravenamme.github.io/2020/02/12/what-is-babel/
https://dev.to/leonardomso/a-complete-react-boilerplate-tutorialfrom-zero-to-hero-jig
https://blog.logrocket.com/why-you-dont-need-babel/
https://dev.to/dchhitarka/introduction-to-programming-compiler-and-interpreter-23a4
https://velog.io/@chumil7432/import-React-from-react%EB%8A%94-%EC%99%9C-%EC%93%B0%EB%8A%94-%EA%B1%B8%EA%B9%8C
https://velog.io/@himprover/%EB%A7%A8%EB%82%A0-%ED%95%9C%EA%B7%80%EB%A1%9C-%EB%93%A3%EA%B3%A0-%ED%9D%98%EB%A6%AC%EB%8A%94-%EB%B0%94%EB%B2%A8Babel%EC%9D%B4-%EB%AD%98%EA%B9%8C
https://cyfyifanchen.com/babel-under-the-hood/
https://dev.to/nakabonne/take-a-walk-the-go-ast-e02
https://velog.io/@chumil7432/import-React-from-react%EB%8A%94-%EC%99%9C-%EC%93%B0%EB%8A%94-%EA%B1%B8%EA%B9%8C
https://yamoo9.gitbook.io/webpack/babel/babel-cli-configure
https://velog.io/@logqwerty/Babel-%EC%BB%B4%ED%8C%8C%EC%9D%BC%EC%9D%84-%EC%82%B4%ED%8E%B4%EB%B3%B4%EC%9E%90
https://kschoi.github.io/cs/babel-config-js-vs-babelrc/
https://velog.io/@logqwerty/Babel-%EC%BB%B4%ED%8C%8C%EC%9D%BC%EC%9D%84-%EC%82%B4%ED%8E%B4%EB%B3%B4%EC%9E%90
JavaScript 코드이지만 브라우저에서 단독으로 실행될 수 없다. Area Code