Posts [프론트엔드 개발환경의 이해와 실습] 바벨의 기본 개념
Post
Cancel

[프론트엔드 개발환경의 이해와 실습] 바벨의 기본 개념

1) 배경

크로스 브라우징

브라우져마다 사용하는 언어가 달라서 프론트엔트 코드는 일관적이지 못할 때가 많다. 스팩과 브라우져가 개선되고 있지만, 여전히 인터넷 익스플로러는 프라미스를 이해하지 못한다. 작년까지만 해도 사파리 최신 브라우져는 Promise.prototype.finally 메소드를 사용할 수 없었다. 프론트엔드 개발에서 크로스브라우징 이슈는 코드의 일관성을 해치고 초심자를 불안하게 만든다. 히브리어로 바벨이 혼돈이란 뜻인 것처럼 말이다.

크로스브라우징의 혼란을 해결해 줄 수 있는 것이 바벨이다. ECMAScript2015 이상 버전으로 작성한 코드를 모든 브라우져에서 동작하도록 호환성을 지켜준다. 타입스크립트, JSX처럼 다른 언어로 분류되는 것도 포함한다.

즉, 정리하자면 모든 브라우저에 모두 동작할 수 있는 코드로 번역(트랜스파일)해주기 위해 탄생하였다.

트랜스파일과 빌드

이렇게 변환하는 것을 트랜스파일 한다라고 표현한다. 변환 전후의 추상화 수준이 다른 빌드와는 달리 트랜스파일은 추상화 수준을 유지한 상태로 코드를 변환한다. 타입스크립트 → 자바스크립트, JSX → 자바스크립트처럼 트랜스파일 후에도 여전히 코드를 읽을 수 있다.

2) 바벨의 기본 도작

바벨은 ECMAScript2015 이상의 코드를 적당한 하위 버전으로 바꾸는 것이 주된 역할이다. 이렇게 바뀐 코드는 IE나 구버전 브라우져처럼 최신 자바스크립트 코드를 이해하지 못하는 환경에서도 잘 동작한다.

바벨을 이용해 아래 코드를 인터넷 익스플로러가 이해할 수 있는 코드로 바꿔 보자.

1
2
// src/app.js:
const alert = msg => window.alert(msg)

먼저 바벨 최신 버전를 설치한다. 터미널 도구를 사용하기 위해 커맨드라인 도구도 함께 설치하자.

1
npm install -D @babel/core  @babel/cli

설치를 완료후 node_modules/.bin 폴더에 추가된 바벨 명령어를 사용할 수 있다.

1
2
npx babel app.js
const alert = msg => window.alert(msg);

바벨은 세 단계로 빌드를 진행한다.

  • 1)파싱(Parsing)
  • 2)변환(Transforming)
  • 3)출력 (Printing)

코드를 읽고 추상 구문 트리(AST)로 변환하는 단계를 파싱 이라고 한다. 즉, const, alert, =, msg 등 하나 하나 분해하는 과정으로 보면 된다.

이것은 빌드 작업을 처리하기에 적합한 자료구조인데 컴파일러 이론에 사용되는 개념이다. 추상 구문 트리를 변경하는 것이 변환 단계이다. 실제로 코드를 변경하는 작업을 한다. 변경된 결과물을 출력하는 것을 마지막으로 바벨은 작업을 완료한다.

그런데 결과를 보면 아래와 같이 빌드 이전과 변한게 하나도 없다.

스크린샷 2022-03-23 오후 8 42 35

3) 플러그인

플러그인이 바로 변환(트랜스파일) 을 담당하는 녀석이다.

커스텀 플러그인

플러그인을 직접 만들면서 동작이 원리를 살펴 보겠다. my-babel-plugin.js 라는 파일을 아래처럼 만들어 보자(바벨 홈페이지의 예제 코드)

  • my-babel-plugin.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module.exports = function myBabelPlugin() {
    return {
      visitor: {
        Identifier(path) {
          const name = path.node.name // 파싱된 결과물에 접근할 수 있다
  
          // 바벨이 만든 AST 노드를 출력한다 (파싱된 결과)
          console.log("Identifier() name:", name)
  
          // 변환작업: 코드 문자열을 역순으로 변환한다
          path.node.name = name.split("").reverse().join("")
        },
      },
    }
  }

플러그인은 visitor 객체를 가진 함수를 반환해야 한다. 이 객체는 바벨이 파싱하여 만든 추상 구문 트리(AST)에 접근할 수 있는 메도르르 제공한다. 그중 Identifier() 메소드의 동작 원리를 살펴보는 코드다.

플러그인 사용법을 알아보자.

1
2
3
npx babel --help

  --plugins [list]                            A comma-separated list of plugin names.

--plugins 옵션에 플러그인을 추가하면 된다.

1
2
3
4
5
6
7
8
9
npx babel app.js --plugins ./my-babel-plugin.js

Identifier() name: alert
Identifier() name: msg
Identifier() name: window
Identifier() name: alert
Identifier() name: msg

const trela = gsm => wodniw.trela(gsm);

Identifier() 메소드로 들어온 인자 path에 접근하면 코드 조각에 접근할 수 있는 것 같다. path.node.name의 값을 변경하는데 문자를 뒤집는 코드다. 결과의 마지막 줄에서 보는것 처럼 이 코드의 문자열 순서가 역전되었다.

우리가 하려는것은 ECMASCript2015로 작성한 코드를 IE 에서 돌리는 것이다. 먼저 const 코드를 var로 변경하는 플러그인을 만들어 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module.exports = function myBabelPlugin() {
    return {
      visitor: {
        // https://github.com/babel/babel/blob/master/packages/babel-plugin-transform-block-scoping/src/index.js#L26
        VariableDeclaration(path) {
            console.log("VariableDeclaration() kind:", path.node.kind) // const
            
            // const -> var 변환
            if (path.node.kind === "const") {
                path.node.kind = "var"
            }
        },
      },
    }
  }

이번에는 vistor 객체에 VariableDeclaration() 메소드를 정의했다. path에 접근해 보면 키워드가 잡히는 걸 알 수 있다. path.node.kind가 const 일 경우 var로 변환하는 코드다.

이 플러그인으로 다시 빌드해보면 아래 이미지와 같이 const 코드가 var로 변경된 것을 확인할 수 있다.

스크린샷 2022-03-23 오후 8 52 01

플러그인 사용하기

실제로 constvar 로 변환해주는 플러그인이 block-scoping플러그인이다.

NPM 패키지로 제공하는 플러그인을 설치하고,

1
npm install -D @babel/plugin-transform-block-scoping

설치한 플러그인을 사용해보면,

1
2
3
npx babel app.js --plugins @babel/plugin-transform-block-scoping

var alert = msg => window.alert(msg);

커스텀 플러그인과 같은 결과를 보인다.

IE는 화살표 함수도 지원하지 않는데 arrow-functions 플러그인을 이용해서 일반 함수로 변경할 수 있다.

1
2
3
4
5
6
7
8
9
npm install -D @babel/plugin-transform-arrow-functions

npx babel app.js \
  --plugins @babel/plugin-transform-block-scoping \
  --plugins @babel/plugin-transform-arrow-functions

var alert = function (msg) {
  return window.alert(msg);
};

ECMAScript5에서부터 지원하는 엄격 모드를 사용하는 것이 안전하기 때문에 "use strict" 구문을 추가해야 겠다. strict-mode 플러그인을 사용하자. 먼저 설치부터 하자.

1
npm i -D @babel/plugin-transform-strict-mode

실행하기에 앞서 커맨드라인 명령어가 점점 길어지기 때문에 설정 파일로 분리하는 것이 낫겠다.

웹팩 webpack.config.js를 기본 설정파일로 사용하듯 바벨도 babel.config.js를 사용한다.

프로젝트 루트에 babel.config.js 파일을 아래와 같이 작성하자.

1
2
3
4
5
6
7
8
// babel.config.js:
module.exports = {
  plugins: [
    "@babel/plugin-transform-block-scoping",
    "@babel/plugin-transform-arrow-functions",
    "@babel/plugin-transform-strict-mode",
  ],
}

커맨드라인에서 사용한 block-scoping, arrow-functions 플러그인을 설정 파일로 옮겼는데 plugins 배열에 추가하는 방식이다. strict-mode 플러그인을 마지막 줄에 추가했다.

  • 다시 빌드해보자.
1
2
3
4
5
6
7
npx babel app.js

"use strict";

var alert = function (msg) {
  return window.alert(msg);
};

상단에 "use strict" 구문이 추가되어 엄격모드가 활성화 되었다. 이제야 비로소 IE에서 안전하게 동작하는 코드로 트랜스파일하였다.

이처럼 변환을 위한 플러그인 목록은 공식 문서의 Plugins 페이지에서 확인할 수 있다.

스크린샷 2022-03-23 오후 9 15 45

4) 프리셋

ECMAScript2015+으로 코딩할 때 필요한 플러그인을 일일이 설정하는 일은 무척 지난한 일이다.

코드 한 줄 작성하는데도 세 개 플러그인 세팅을 했으니 말이다. 목적에 맞게 여러가지 플러그인을 세트로 모아놓은 것“프리셋”이라 한다.

커스텀 프리셋

사용한 세 개 플러그인을 하나의 프리셋으로 만들어 보자. my-babel-preset.js 파일을 다음과 같이 작성하자.

  • my-babel-preset.js
1
2
3
4
5
6
7
8
9
module.exports = function myBabelPreset() {
    return {
        plugins: [
            "@babel/plugin-transform-block-scoping",
            "@babel/plugin-transform-arrow-functions",
            "@babel/plugin-transform-strict-mode",
        ]
    }
}

plugins 배열에 사용한 세 개 플러그인을 담았다.

프리셋을 사용하기 위해 바벨 설정을 약간 수정한다.

  • babel.config.js
1
2
3
module.exports = {
  presets: ["./mypreset.js"],
}

플러그인 세팅 코드를 제거하고 presets에 방금 만든 my-babel-preset.js를 추가했다. 실행해보면 동일한 결과를 출력할 것이다.

스크린샷 2022-03-23 오후 9 20 28

Note: 실제 실무에서 위와 같은 과정들이 사용되지 않는다. 단순하게 어떻게 바벨이 동작하는지만 이해하면 된다.

출처

This post is licensed under CC BY 4.0 by the author.

[프론트엔드 개발환경의 이해와 실습] 바벨 사용법과 웹팩 통합

[프론트엔드 개발환경의 이해와 실습] ESLint