프론트 개발 환경 구성 - babel

ddullgi·2023년 5월 1일
0
post-thumbnail

Babel

세상에는 다양한 종류의 브라우저들이 있고 각 브라우저마다 지원하는 언어의 버전이나 문법의 세부적이 동작이 다릅니다. 심지어 같은 브라우저라도 사용자가 사용하는 브라우저의 버전에 따라서도 차이가 나기도 합니다.

그렇다고 개발자가 브라우저 별로 하나하나 개별로 코드를 작성하는 것은 현실적이지 않습니다. babel은 이러한 크로스브라우징 이슈를 해결해 줄 수 있는 유용한 도구입니다. 개발자가 ECMAScript2015 이상의 문법으로 코드를 작성한다면 babel은 이 코드를 적당한 하위 버전의 Javascript 코드로 변환해 줍니다. 이러한 과정을 transpile이라고 하며 babel은 Javascript 트렌스파일러입니다.


Babel의 동작과정

babel은 다음과 같은 3가지 과정을 통해 동작합니다.


1. 파싱(parsing)

코드를 읽고 이 코드를 추상 구문 트리(AST)로 변환하는 과정을 파싱이라고 부릅니다.

2. 변환(transforming)

추상 구문 트리를 변환하는 과정입니다.

3. 출력(printing)

추상 구문 트리를 다시 코드의 형태로 되돌리는 과정입니다.


하지만 babel을 받은 후 기본 설정으로 코드를 트렌스파일링 하면 코드가 전혀 바뀌지 않는 것을 알 수 있습니다. 이는 바벨의 기본 동작은 사실 파싱과 출력 과정만 동작하기 때문입니다.


플러그인

바벨은 파싱과 출력만 담당하고 변환 작업은 다른 녀석이 처리하데 이것을 플러그인 이라고 부릅니다.

자주 사용되는 플러그인은 @babel/plugin-transform-arrow-functions, @babel/plugin-transform-block-scoping 등이 있는데 각각 에로우 함수와 블록 스코핑을 변환해주는 플러그인이다.


프리셋

하지만 babel을 사용하기 위해 필요한 다양한 플러그인들을 일일이 설정하는 일은 매우 번거로운 일입니다. 이러한 다양한 플러그인들을 목적에 맞게 세트로 모아둔 것을 프리셋이라고 부릅니다.


자주 사용하는 플러그인

preset-env

ECMAScript2015+의 코드를 이전 버전의 코드로 변환할 때 사용된다.

preset-react

리액트 코드를 Javascript 코드로 변환하기 위해 사용된다.

preset-typescript

타입스크립트 코드를 Javascript 코드로 변환하기 위해 사용된다.


설치

$ npm install -D @babel/core @babel/cli @babel/preset-env core-js

설정 파일 작성

babel.config.js

module.exports = {
  presets: [
    [
      // preset-env는 ECMAScript2015+를 변환할 때 사용한다.
      "@babel/preset-env",
      {
        // 목표로 하는 브라우저의 버전을 설정한다.
        targets: {
          chrome: "79", // 크롬 79까지 지원하는 코드를 만든다.
          ie: "11",
        },
      },
    ],
  ],
};
  • targets: 목표하는 브라우저의 버전을 설정하면 그에 맞게 변환시켜 준다.

폴리필

babel은 플러그인을 이용하여 javascript 코드를 같은 동작을 하는 이전의 문법으로 변환해 준다. 그렇다면 대응하는 이전 버전의 문법이 없다면 어떻게 될까요?

다음 문법은 ECMASCript2015에서 추가된 Promise 문법입이다.

new Promise()

이 코드를 env 프리셋으로 변환한다면 어떻게 될까요?

image-20230501204401911

결과를 보면 변환 되지 않고 그대로 나온 것을 알 수 있다. 이를 ECMASCript2015를 지원하지 않는 브라우저에서 그대로 실행한다면 다음과 같은 결과가 나옵니다.

image-20230501204619057

예상과는 달리 Promise가 env 프리셋으로 변환되지 않았습니다. 이유는 에로우 함수 처럼 이전 버전에서 Promise를 대응하는 문법이 존재하지 않기 때문입니다.

이처럼 이전 버전의 문법으로 대응할 수 없는 문법들을 트렌스컴파일 할 때 폴리필을 이용합니다. 폴리필은 Promise와 같이 이전 버전에 대응 할 수 없는 문법을 이전 버전의 문법으로 재현한 코드들의 모음을 말합니다. 즉, 폴리필을 설정하고 Promise를 트렌스컴파일 한다면 설정한 폴리필에서 Promise를 재현한 이전 버전의 코드를 불러와 트렌스컴파일 해줍니다.


폴리필 설정

// babel.config.js (root 폴더에 생성)
module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        targets: {
          chrome: "79", 
          ie: "11",
        },
        // babel은 작성한 코드를 es5에 대체가능한 코드로만 변환을 실행한다. 
		// 즉, 대체할 문법이 없다면 변환하지 않는다. 
        // 이를 해결하기 위해 변환 할 수 없는 문법을 es5로 구현한 코드조각을 추가해서 해결한다. 
        // 이 코드 조각을 모아 놓은 것을 폴리필이라고 한다. 주로 coreJS를 많이 사용한다.
        useBuiltIns: "usage", // 폴리필 사용 방식 지정 기본값은 false
        corejs: {
          // 폴리필 버전 지정
          version: 3,
        },
      },
    ],
  ],
};
  • useBuiltIns: 폴리필의 사용 방식을 지정합니다. 기본 값은 false
  • corejs: 주로 사용하는 폴리필입니다. @babel/core에 내장 되어 있습니다.

폴리필을 설정한 이후에 다시 변환을 실행해 보았다.

image-20230501225904365

require("core-js/modules/es.object.to-string.js");require("core-js/modules/es.promise.js"); 라는 코드가 추가 된 것을 볼 수 있습니다. 이는 core-js 폴리필에서 Promise 코드를 불러온 것입니다.


webpack과 통합

지금까지의 설정만으로는 cli 명령어로 파일 하나하나 트렌스컴파일을 해야합니다. 이를 webpack과 통합하여 build 명령 한번만으로 실행해 봅시다.

webpack에서는 babel을 함께 쓰기 위한 loader인 babel-loader 제공합니다.

$ npm install -D babel-loader

webpack 설정에 로더를 추가합니다.

// webpack.config.js:
module.exports = {
  module: {
    rules: [
      // ...
      {
        test: /\.(ts|tsx|js|jsx)$/,
        exclude: /node_modules/,
        loader: "babel-loader",
      },
    ],
  },
}
  • test: 적용 범위를 정규표현식으로 지정
  • exclude: 제외할 범위 지정


추상 구문 트리(AST)

소스 코드에서 발생되는 구조를 트리로 표현한 것입니다.. 이 설명 만으로는 이해하기 힘드니 예시를 들어보겠습니다.

(foo, bar) => foo + bar;

해당 코드를 추상 구문 트리로 바꿔본다면 다음과 같이 변환됩니다.

// ...
		"expression": {
          "type": "ArrowFunctionExpression",
          "start": 0,
          "end": 23,
          "loc": {
            "start": {
              "line": 1,
              "column": 0
            },
            "end": {
              "line": 1,
              "column": 23
            }
          },
          "id": null,
          "generator": false,
          "expression": true,
          "params": [
            {
              "type": "Identifier",
              "start": 1,
              "end": 4,
              "loc": {
                "start": {
                  "line": 1,
                  "column": 1
                },
                "end": {
                  "line": 1,
                  "column": 4
                }
              },
              "name": "foo"
            },
            {
              "type": "Identifier",
              "start": 6,
              "end": 9,
              "loc": {
                "start": {
                  "line": 1,
                  "column": 6
                },
                "end": {
                  "line": 1,
                  "column": 9
                }
              },
              "name": "bar"
            }
          ],
          "body": {
            "type": "BinaryExpression",
            "start": 14,
            "end": 23,
            "loc": {
              "start": {
                "line": 1,
                "column": 14
              },
              "end": {
                "line": 1,
                "column": 23
              }
            },
            "left": {
              "type": "Identifier",
              "start": 14,
              "end": 17,
              "loc": {
                "start": {
                  "line": 1,
                  "column": 14
                },
                "end": {
                  "line": 1,
                  "column": 17
                }
              },
              "name": "foo"
            },
            "operator": "+",
            "right": {
              "type": "Identifier",
              "start": 20,
              "end": 23,
              "loc": {
                "start": {
                  "line": 1,
                  "column": 20
                },
                "end": {
                  "line": 1,
                  "column": 23
                }
              },
              "name": "bar"
            }
          }
        }
// ...

트리가 너무 긴 관계로 에로우 함수에 해당되는 부분만 가져왔는데 총 줄 수가 440줄이 될 정도로 깁니다. 전문은 이 곳으로 가면 볼 수 있습니다.

해당 부분을 보면 에로우 함수에 대한 params, body, operator 등의 타입과 위치 등 코드에 대한 자세한 정보를 트리 형태로 정리 되어 있는 것을 알 수 있습니다.


참고 자료

프론트엔드 개발환경의 이해: Babel
프론트엔드 개발환경의 이해와 실습 (webpack, babel, eslint..)

profile
프론트엔드개발자를 꿈꾸는 예비 개발자

0개의 댓글