Babel에 대해

Babel에 대해 자세히 알아보자.

1. Babel이란

Babel은 최신 JavaScript 문법을 구형 브라우저에서도 실행 가능하게 변환해주는 트랜스파일러(transpiler) 입니다. 이를 통해 개발자는 최신 문법으로 코드를 작성하면서도, 다양한 환경에서 실행될 수 있도록 도와줍니다.

  • Transpiler: Babel은 코드를 다른 형태로 변환해 같은 수준의 언어로 바꿉니다. 엄밀히 말하면 트랜스파일러지만, 컴파일러의 일종으로 볼 수 있습니다.
  • 최신 JavaScript 지원: Babel을 사용하면 구형 브라우저 호환성을 걱정하지 않고 최신 문법을 자유롭게 사용할 수 있습니다.

2. Babel의 동작 과정

Babel은 세 가지 주요 단계를 통해 코드를 변환합니다

1. 파싱(Parsing)

  • Babel은 소스 코드를 AST(Abstract Syntax Tree, 추상 구문 트리)로 변환합니다.
  • AST는 코드의 구조를 트리 형태로 표현한 것이며, 이를 기반으로 코드가 해석됩니다.

2. 변환(Transform)

  • 생성된 AST를 사용해 개발자가 설정한 플러그인과 프리셋에 따라 AST를 수정하여 변환 작업을 수행합니다.

3. 코드 생성(Generate)

  • 변환된 AST는 다시 코드로 변환되어, 하위 호환성을 가진 새로운 코드가 만들어집니다.

AST란

AST(추상 구문 트리)는 소스 코드의 구조를 트리 형태로 나타낸 것입니다. Babel은 이 AST를 바탕으로 코드를 분석하고 변환합니다.

  • 코드의 구성 요소(변수, 함수, 연산자 등)가 트리의 노드로 표현됩니다.
  • AST는 코드의 구조를 명확하게 이해하고 변형하는 데 중요한 역할을 합니다.

Babel 설정 파일 (Babel Config)

Babel은 설정 파일을 통해 플러그인과 프리셋을 제어할 수 있습니다. 가장 많이 사용되는 두 가지 설정 파일은 다음과 같습니다

  1. babel.config.json
  • 루트 폴더에 생성되며, 프로젝트 전체에 적용됩니다.

  • Monorepo와 같은 구조에서 유용하며, 전체 프로젝트에서 통일된 설정을 적용할 수 있습니다.

  • Babel 7부터 .babelrc 대신 babel.config.json을 권장합니다.

    {
      "presets": [
        [
          "@babel/env",
          {
            "targets": {
              "edge": "17",
              "firefox": "60",
              "chrome": "67",
              "safari": "11.1"
            }
          }
        ]
      ]
    }
  1. .babelrc.json
  • 프로젝트의 특정 파일이나 폴더에만 적용되는 설정 파일입니다.
  • 개별 패키지나 모듈에 맞춘 설정이 필요할 때 사용합니다.

Babel과 Webpack 연동

Babel을 Webpack과 함께 사용하려면 babel-loader를 설치하여 사용합니다.

1.Babel Loader 설정

  • babel-loader는 Babel을 Webpack의 로더로 실행시키는 역할을 합니다.
  • Webpack 설정 파일에서 Babel을 설정한 후, 프로젝트 번들링 시 Babel이 코드 트랜스파일링을 진행합니다.
  • node_modules는 트랜스파일링이 필요 없으므로, exclude 옵션으로 제외해야 성능을 최적화할 수 있습니다.
    // webpack.config.js
    module.exports = {
      module: {
        rules: [
          {
            test: /\.(tsx|ts)$/,
            exclude: /node_modules/,
            use: {
              loader: 'babel-loader',
              options: {
                presets: [
                  '@babel/preset-env',
                  '@babel/preset-react',
                  '@babel/preset-typescript',
                ],
                plugins: [
                  '@babel/plugin-transform-runtime',
                  'babel-plugin-styled-components',
                ],
              },
            },
          },
        ],
      },
    };

Babel의 플러그인(Plugins)과 프리셋(Presets)

1. 플러그인(Plugins)

  • Babel에서 코드 변환은 플러그인을 통해 이루어집니다.
  • 각 플러그인은 Babel 컴파일 과정에서 AST를 변형하는 역할을 하며, 변형된 AST를 가지고 타겟 코드를 생성합니다.

2. 프리셋(Presets)

  • 프리셋은 플러그인들의 집합입니다.
  • 여러 플러그인을 하나씩 추가하는 대신, 그룹으로 묶어 한 번에 설정할 수 있습니다.
  • 대표적인 Preset들:
    • @babel/preset-env: 최신 자바스크립트 문법을 ES5로 변환
    • @babel/preset-react: React의 JSX를 변환
    • @babel/preset-typescript: TypeScript 문법 지원

각 프리셋은 해당 문법을 변환하는 데 필요한 플러그인들을 포함하고 있습니다.


Babel과 React

Babel을 사용하면 React의 JSX 문법도 트랜스파일링할 수 있습니다.

  • @babel/preset-react은 JSX 코드를 React의 React.createElement 함수 호출로 변환합니다.

    // babel 컴파일 전
    const profile = (
      <div>
        <img src='profile.png' className='profile' />
        <h1>{[user.firstName, user.lastName].join(' ')}</h1>
      </div>
    );
    // babel 컴파일 후
    const profile = React.createElement(
      'div',
      null,
      React.createElement('img', { src: 'profile.png', className: 'profile' }),
      React.createElement('h1', null, [user.firstName, user.lastName].join(' '))
    );

Babel과 TypeScript

Babel은 TypeScript를 트랜스파일링할 수도 있습니다.

  • @babel/preset-typescript를 사용하여 TypeScript 코드를 변환할 수 있지만, 타입 체킹은 Babel에서 지원하지 않습니다.
  • 타입 체킹은 ts-loader와 같은 별도의 도구를 사용하여 처리해야 하며, Babel과 함께 사용하는 경우 fork-ts-checker-webpack-plugin과 같은 플러그인이 필요합니다.

Polyfill과 Babel

Babel은 최신 자바스크립트 문법을 변환하지만, 모든 최신 기능을 지원하는 것은 아닙니다. 특히, Promise나 WeakMap 같은 최신 객체는 변환되지 않으므로 Polyfill이 필요합니다.

1. Polyfill이 필요한 이유

  • 구형 브라우저에서는 최신 문법과 객체를 지원하지 않기 때문에, 런타임에 특정 기능을 추가해야 합니다.

2. @babel/polyfill

  • 과거에는 @babel/polyfill이 사용되었으나, 최근에는 @babel/plugin-transform-runtime과 core-js@3를 사용하는 방식이 권장됩니다.

    // webpack.config.js 설정
    module.exports = {
      plugins: [
        [
          '@babel/plugin-transform-runtime',
          {
            corejs: 3, // core-js 설정
            regenerator: true,
          },
        ],
      ],
    };

Babel에서 Promise 처리

Babel은 ES6에서 등장한 Promise를 구형 브라우저에서도 사용할 수 있도록 변환합니다.

  • async/await 문법은 generator와 대응되며, regenerator-runtime을 통해 변환됩니다.

    // ES6의 async-await
    async function testFunc() {
      let value = await promise;
      console.log(value);
    }
     
    // Babel로 변환된 코드
    function _asyncToGenerator(fn) {
      return function () {
        var self = this,
          args = arguments;
        return new Promise(function (resolve, reject) {
          var gen = fn.apply(self, args);
          function _next(value) {
            asyncGeneratorStep(
              gen,
              resolve,
              reject,
              _next,
              _throw,
              'next',
              value
            );
          }
          function _throw(err) {
            asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'throw', err);
          }
          _next(undefined);
        });
      };
    }
    1. async/await와 generator 변환

    • async 키워드는 generator 함수로 변환되며, await 키워드는 yield에 대응됩니다.

    • Babel은 이러한 변환을 통해 비동기 코드를 동기적 흐름처럼 사용할 수 있게 합니다.

    1. 이터레이터 메서드 호출

    • 하나의 로직이 종료될 때마다 이터레이터 객체의 메서드인 next()가 호출되어 다음 로직을 실행합니다.

    • next() 메서드는 비동기적 패턴을 관리하는데, 이터레이터의 반환값이 완료 상태(done)라면 resolve로 값을 반환합니다.

    1. 재귀적 Promise 호출

    • 반환값이 완료되지 않은 경우, Promise를 다시 재귀적으로 호출하여 비동기 흐름을 처리합니다.

    • 이때 비동기 처리의 흐름을 제어하는 부분은 Babel에서 변환된 generator 함수가 담당합니다.

    1. regenerator 라이브러리 사용

    • ES5에는 generator가 정의되지 않았기 때문에 Babel은 regenerator 라이브러리를 사용해 generator를 흉내낸 함수를 구현합니다.

    • 여기서 Babel이 생성하는 함수가 _asyncToGenerator입니다.

    1. Promise와 generator의 협력

    • generator는 비동기적인 패턴을 yield를 통해 동기적인 흐름처럼 처리할 수 있도록 바꿔줍니다.
    • Promise는 generator로 만들어진 이터레이터를 반복해서 실행하여 비동기 흐름을 관리합니다.
    • await 키워드가 사용된 함수는 항상 Promise를 반환해야 하는 이유도, 이처럼 generator와 Promise가 협력해 비동기 로직을 처리하기 때문입니다.

참고