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은 최신 자바스크립트 문법을 변환하지만, 모든 최신 기능을 지원하는 것은 아닙니다. 특히, PromiseWeakMap 같은 최신 객체는 변환되지 않으므로 Polyfill이 필요합니다.

1. Polyfill이 필요한 이유

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

2. @babel/polyfill

  • 과거에는 @babel/polyfill이 사용되었으나, 최근에는 @babel/plugin-transform-runtimecore-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/awaitgenerator 변환

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

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

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

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

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

    1. 재귀적 Promise 호출

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

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

    1. regenerator 라이브러리 사용

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

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

    1. Promisegenerator의 협력

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

참고