바벨에 대해 알아보자

방구석 코딩쟁이·2024년 2월 2일
0

FE개발환경

목록 보기
5/5

배경

1. 크로스 브라우징

프론트엔드 개발에서의 크로스 브라우징 이슈는 코드의 일관성을 해치게 됩니다. 이러한 크로스 브라우징의 혼란을 해결해 줄 수 있는 것이 바벨이며, ECMAScript2015 이상의 문법으로 작성한 코드를 모든 브라우저에서 동작하도록 호환성을 지켜줍니다. 또한, Typescript, JSX와 같이 다른 언어로 작성된 코드도 브라우저에서 작동하도록 변환해줍니다.

2. 트랜스 파일과 빌드

코드를 변환시키는 것을 "트랜스파일"한다고 말합니다. 변환 전/후의 추상화 수준이 다른 빌드와 달리 트랜스파일은 추상화 수준을 유지한 상태로 코드를 변환하는데, 최근에는 이 둘을 명확하게 구분하지 않는 편입니다.

기본 동작

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

먼저 src 디렉토리 안에 alert.js 파일을 만들어봅시다.

바벨을 이용해 소스코드를 IE가 이해할 수 있는 코드로 바꿔보도록 하겠습니다.

//src/alert.js
const alert = msg => window.alert(msg);

먼저 바벨 최신 버전을 설치하고, 터미널에서 명령어로 바벨을 사용하기 위해 커맨드라인 도구도 같이 설치해봅시다.
npm i -D @babel/core @babel/cli

설치를 하고 나면, node_modules/.bin 폴더에 추가된 babel 명령어을 사용할 수 있게 됩니다.

npx babel ./src/alert.js 또는 node_modules/.bin/babel ./src/alert.js 명령어를 통해 바벨을 실행해봅시다.

  • npx를 통해 설치된 babel 명령어를 사용할 수도 있습니다.

바벨은 3 단계를 거쳐 빌드를 진행합니다.

  1. 파싱(Parsing)
    코드를 읽고 추상 구문 트리(AST)로 변환하는 단계
    빌드 작업을 처리하기에 적합한 자료구조로써 컴파일러 이론에 사용되는 개념
  2. 변환(Transforming)
    추상 구문 트리를 실제 코드로 변경하는 단계
  3. 출력(Printing)
    변경된 결과물을 출력하는 단계입니다.
    이 과정을 마지막으로 바벨은 작업을 완료합니다.

이제 결과를 보도록 합시다.

그런데 터미널에 보여진 결과를 보면 이전과 달라진게 없는 것 같습니다.

플러그인

기본적으로 바벨은 코드를 받아서 코드를 반환합니다. 바벨을 함수로 정의한다면 아래와 같은 모습일 것입니다.
const babel = code => code

바벨은 파싱과 출력만을 담당하고, 변환 작업은 플러그인에게 맡깁니다. 이를 코드로 나타내면 아래와 유사한 모습일 것입니다.

const plugins = [plugin함수1, plugin함수2, ... ]
const babel = code => plugins.map(fn => fn(code));

그러면 커스텀 플러그인을 만들면서 바벨이 어떤 역할을 하는지 보도록 합시다.

1) 커스텀 플러그인 만들기

루트경로에서 babel이라는 디렉토리를 생성하고 babelPlugin.js라는 파일을 만들고 나서 아래의 코드를 작성해봅시다.

// babel/babelPlugin.js
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('');
      },
    },
  };
};
  • Identifer라는 메서드가 visitor 객체에 들어있습니다.
  • path 객체의 node.name 속성을 통해 파싱된 결과물에 접근할 수 있게 됩니다.

이제 이 플러그인을 사용하여 바벨을 실행하도록 해봅시다.
npx babel ./src/alert.js --plugins './babel/babelPlugin.js' 명령어를 입력해보시면 다음과 같은 결과를 보시게 될 것입니다.

자바스크립트의 예약어(예시const, =>)를 제외한 모든 값(파싱되어진 토큰)들이 뒤집혀져 나왔음을 볼 수 있습니다.

이번에는 constvar로 변경하는 플러그인을 만들어봅시다. 기존의 플러그인을 아래와 같이 수정해보죠.

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

        if (path.node.kind === "const") {
          path.node.kind = "var"
        }
      },
    },
  }
}
  • vistor 객체에 VariableDeclaration() 메소드를 정의하였습니다.
  • pathnode.kind를 통해 변수 정의 키워드(const, var, let)에 접근할 수 있습니다.

이 플러그인을 적용시켜서 빌드를 해봅시다
npx babel ./src/alert.js --plugins './babel/babelPlugin.js'명령어를 입력하게 되면 다음과 같은 결과를 얻게 됩니다.

마지막 줄에 집중해보면 const 코드가 var로 변경되었음을 알 수 있습니다.

2) 플러그인 사용하기

block-scoping 플러그인을 사용하면, 블록 스코프를 가지는 예약어(const, let)을 함수 스코프를 가지는 예약어(var)로 변경해줍니다.

먼저 플러그인을 설치해보도록 합시다.
npm install -D @babel/plugin-transform-block-scoping

이 플러그인을 적용시켜서 빌드를 해봅시다.
npx babel ./src/alert.js --plugins @babel/plugin-transform-block-scoping명령어를 입력하면 다음과 같은 결과를 얻게 됩니다.

이번에는 화살표 함수를 함수 선언문으로 바꿔주는 arrow-functions 플러그인을 추가해보도록 해봅시다.
npm install -D @babel/plugin-transform-arrow-functions

이 플러그인을 적용시켜서 빌드를 해봅시다.
npx babel ./src/alert.js --plugins @babel/plugin-transform-block-scoping --plugins @babel/plugin-transform-arrow-functions 명령어를 입력하면 다음과 같은 결과를 얻게 됩니다.

이번에는 코드에 "use strict"를 추가하는 strict-mode 플러그인을 사용해보도록 하겠습니다.

먼저 플러그인을 설치해봅시다.
npm install -D @babel/plugin-transform-strict-mode

그 다음에 npx babel ./src/alert.js --plugins @babel/plugin-transform-block-scoping --plugins @babel/plugin-transform-arrow-functions --plugins @babel/plugin-transform-strict-mode 명령어를 입력하면 다음과 같은 결과를 얻게 됩니다.

근데 바벨을 실행하기 위한 커맨드라인 명령어가 너무 길어지기 때문에 설정 파일로 분리하는 것이 좋습니다.
바벨 기본 설정 파일은 babel.config.js입니다. 프로젝트 루트 경로에 설정 파일을 추가해주고, 아래와 같이 작성해봅시다.

// babel.config.js:
module.exports = {
  plugins: [
    "@babel/plugin-transform-block-scoping",
    "@babel/plugin-transform-arrow-functions",
    "@babel/plugin-transform-strict-mode",
  ],
}

npx babel ./src/alert.js 명령어로 다시 바벨을 실행해봅시다

그럼 바벨과 트랜스파일에 대해 어느정도 이해가 되셨으리라 생각이 됩니다. 변환을 위한 플러그인 목록은 공식 문서의 Plugins 페이지에서 확인할 수 있으며 링크를 남겨두겠습니다.

프리셋

필요한 플러그인을 일일이 찾아서 설정하는 것은 매우 힘들고 귀찮습니다. 목적에 맞게 여러가지 바벨 플러그인을 세트로 모아놓은 것을 "프리셋"이라고 합니다.

1) 커스텀 프리셋

먼저 우리가 사용했던 3개의 플러그인을 하나의 프리셋으로 만들어 놓은후에 적용을 시켜보겠습니다.

먼저 루트 디렉토리에 presets 디렉토리를 생성하고, myPreset.js 파일을 만듭시다. 그리고 다음과 같이 함수를 만드면 됩니다.

// presets > myPreset.js
module.exports = function mypreset() {
  return {
    plugins: [
      "@babel/plugin-transform-arrow-functions",
      "@babel/plugin-transform-block-scoping",
      "@babel/plugin-transform-strict-mode",
    ],
  }
}

그런 다음 프리셋을 사용하기 위해 babel.config.js를 다음과 같이 수정해보죠.

// babel.config.js:
module.exports = {
  presets: ["./presets/myPreset.js"],
};
  • 플러그인 세팅 코드를 제거하고, presets에 커스텀 프리셋을 추가했습니다.

npx babel ./src/alert.js 명령어로 실행하면 동일한 결과를 출력하게 됩니다.

2) 프리셋 사용하기

바벨은 목적에 따라 몇 가지 프리셋을 제공하며, 링크를 클릭하면 어떤 프리셋을 제공하는지 확인할 수 있습니다.

대표적인 프리셋들

  • preset-env
    ECMAScript2015+를 변환할 때 사용합니다.
    바벨 7 이전 버전에는 연도별로 프리셋을 제공했지만 지금은 env 하나로 합쳐졌다고 합니다.
    (바벨 7이전에는 babel-reset-es2015, babel-reset-es2016, babel-reset-es2017, babel-reset-latest)
  • preset-flow
    flow를 변환하기 위한 프리셋입니다.
  • preset-react
    react를 변환하기 위한 프리셋입니다.
  • preset-typescript
    타입스크립트를 변환하기 위한 프리셋입니다.

이번 포스트에서는 env 프리셋만 사용해볼 예정입니다.
먼저 패키지를 다운로드합시다.
npm install -D @babel/preset-env

바벨 설정을 다음과 같이 변경해보도록 합시다.

// babel.config.js:
module.exports = {
  presets: ["@babel/preset-env"],
};

npx babel ./src/alert.js 명령어로 바벨을 실행해보면 다음과 같은 결과를 출력하게 되는 걸을 보실 수 있으셨을 겁니다.

preset-env 설정과 폴리필

1) 타겟 브라우저

서비스가 IE를 지원하지 않는다고 한다면 IE를 위한 코드 변환은 불필요합니다. target 옵션에 브라우저 버전명을 지정하면 preset-env는 이에 맞는 플러그인들을 찾아 최적의 코드를 출력해냅니다.

// babel.config.js
module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        targets: {
          chrome: "79", // 크롬 79까지 지원하는 코드를 만든다
        },
      },
    ],
  ],
}

npx babel ./src/alert.js 명령어로 바벨을 실행해보면 "use strict"만 추가해줬음을 보셨을 겁니다.

왜냐하면 크롬은 블록 스코프와 화살표 함수를 지원하기 때문입니다. IE도 지원해야 한다면 다음과 같이 바벨 설정에 브라우저 정보를 추가해주면 됩니다.

// babel.config.js
module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        targets: {
          chrome: "79", // 크롬 79까지 지원하는 코드를 만든다
          ie: "11", // ie 11까지 지원하는 코드를 만든다
        },
      },
    ],
  ],
};

npx babel ./src/alert.js 명령어로 바벨을 실행해보면 IE에서도 돌아가는 코드로 변환해줬음을 알 수 있을 겁니다.

2) 폴리필

이번에는 변환과 조금 다른 폴리필에 대해서 알아보도록 해봅시다
ECMAScript2015에서 나온 Promise 객체를 사용하는 코드를 사용할 겁니다.

먼저 src 디렉토리에 promiseTest.js 파일을 만들고 다음과 같은 코드를 작성해봅시다.

new Promise();

먼저 이전과 동일하게 npx babel ./src/promiseTest.js 명령어를 통해 바벨로 처리해봅시다.

처리 결과를 봤는데 Promise는 변환되지 못한 채로 반환되었습니다. 이 코드를 그대로 IE에서 실행하면 아래와 같은 에러를 던질 것입니다.

플러그인이 Promise 객체를 ECMAScript5 버전으로 변환할 것으로 기대했지만 변환해주지 못했습니다. 왜냐하면 바벨은 ECMAScript2015+를 ECMAScript5 버전으로 변환할 수 있는 것만 빌드하기 때문입니다.

그렇다면 Promise와 같은 문법을 ie에서 돌아갈 수 있도록 변환은 못하는 것일까요??
babel에서는 "폴리필"이라고 부르는 코드 조각을 추가해서 해결해줄 수 있도록 도와줍니다.

가령 ECMAScript2015의 블록 스코핑은 ECMASCript5의 함수 스코핑으로 대체할 수 있다. 화살표 함수도 일반 함수로 대체할 수 있다. 이런 것들은 바벨이 변환해서 ECMAScript5 버전으로 결과물을 만든다.

한편 프라미스는 ECMAScript5 버전으로 대체할 수 없다. 다만 ECMAScript5 버전으로 구현할 수는 있다(참고: core-js promise).

preset-env는 폴리필을 지정할 수 있는 옵션을 제공합니다. 바벨 설정을 다음과 같이 변경해봅시다.

// babel.config.js:
module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        useBuiltIns: "usage", // 폴리필 사용 방식 지정
        corejs: {
          // 폴리필 버전 지정
          version: 2,
        },
      },
    ],
  ],
}
  • useBuiltIns는 어떤 방식으로 폴리필을 사용할지 설정하는 옵션입니다.
    "usage", "entry", false 세 가지 값을 사용하는데 기본값이 false 이므로 폴리필이 동작하지 않았던 것이죠.
    "usage""entry"를 설정하면 폴리필 패키지 중 core-js를 모듈로 가져옵니다.
  • corejs 속성에서 version 옵션을 통해 core-js 모듈의 버전도 명시할 수 있습니다.
  • useBuiltIns 옵션을 확인할 수 있는 링크
  • corejs를 확인할 수 있는 링크

이제 폴리필을 추가했으니 다시 npx babel ./src/promiseTest.js 명령어로 실행해봅시다.

core-js로부터 Promise 모듈을 가져오는 require() 구문이 상단에 추가되었습니다. 이제 IE에서도 Promise를 안전하게 돌아가게 되었습니다.

웹팩으로 통합

일반적으로 바벨을 직접 사용하는 것보다는 웹팩으로 통합해서 사용하는 것이 일반적이다. 로더 형태로 제공하며, 패키지명은 babel-loader입니다.

먼저 패키지를 설치합시다.
npm install -D babel-loader

웹팩 설정에 babel-loader를 추가해줍시다.

module: {
  rules: [
    {
      test: /\.js$/,
      exclude: /node_modules/,
      loader: "babel-loader", // 바벨 로더를 추가한다
    },
    ... //다른 로더 설정들
  ]
}
  • .js 확장자로 끝나는 파일은 babel-loader가 처리하도록 설정했으며, 사용하는 써드파티 라이브러리에는 적용하지 않기 위해 node_modules 폴더 내부의 js 파일은 로더가 처리하지 않도록 예외처리를 했습니다. (관련 글)

폴리필 사용 설정을 했다면 core-js도 설치해야한다. 웹팩은 babel-loader가 만든 아래 코드를 만나면 core-js를 찾을 것이기 때문입니다.

core-js 버전 2로 설치해봅시다.
npm i core-js@2

그리고 promiseTest를 빌드할 것이므로 잠깐 webpack의 entry 설정을 변경해두도록 합시다.

entry: {
  // main: "./src/app.js",
  main: "./src/promiseTest.js"
},

그리고 나서 npm run build 명령어로 웹팩으로 빌드를 해봅시다.

그러면 잘 빌드되어 있음을 확인할 수 있습니다.

출처

profile
풀스택으로 나아가기

0개의 댓글