프론트엔드 개발 환경 공부 #9 바벨의 기본 개념 (플러그인, 프리셋, 폴리필)

Jake Seo·2021년 9월 19일
7

바벨이 나오게 된 배경

성경 창세기전에 나오기를 당시 사람들이 바벨탑을 쌓아 그 꼭대기가 하늘에 닿게 하려고 했다. 그런데, 하나님은 사람들의 그 오만함에 분노했고 사람들이 협업하는데 꼭 필요한 소통수단인 언어를 제각각으로 만들어버렸다고 한다.

히브리어로 '바벨'은 '혼돈'을 뜻한다.

크로스 브라우징

웹개발에서도 개발자마다 프론트엔드에서 사용하는 언어가 다르다. 주로 사용되는 건 JS지만, 최신 문법을 사용할 수도 있고 안할 수도 있으며, TS를 사용할 수도 있다. 그리고 브라우저가 해석할 수 있는 언어 또한 다르다. 스펙과 브라우저는 날이갈수록 개선되고 있지만, 한국에서 약간의 점유율을 가지고 있는 인터넷 익스플로러는 최신 문법을 여전히 지원하지 않는다. 작년까지만해도 사파리 최신 브라우저에서는 Promise.prototype.finally 메소드를 사용할 수 없었다.

보통 개발자들 사이에서 '여러 브라우저에서 작성한 코드가 정상적으로 돌아갈 수 있게 만드는 행위'를 '크로스 브라우징을 지원하게 만든다'고 한다. 바벨을 통해 크로스 브라우징을 지원하게 만들 수 있다. 바벨은 ECMAScript2015 이후 최신 문법으로 작성한 코드를 모든 브라우저에서 동작하도록 호환성을 지켜준다. 또한, 타입스크립트, JSX처럼 다른 언어로 분류되는 것도 포함된다.

크로스 브라우징은 처음 프론트엔드 개발에 접근하는 개발자에게 어려움을 준다.

트랜스파일이란?

바벨은 트랜스파일러(transpiler)로 불린다. A라는 소스코드 파일을 읽어서 B라는 소스코드로 파일로 변환시키는 행위를 트랜스파일(transpile) 이라고 한다.

  • 자바와 같이 컴파일 했을 때, .java파일에서 .class 파일로 변하는 것과 같이 변환 전 후의 수준이 다른 '빌드'의 개념과 달리, '트랜스파일'은 추상화 수준이 같은 코드로 변환한다.
  • '트랜스파일'의 다른 목적은 구버전의 소스코드를 신버전에 호환되는 형식으로 바꾸는데도 이용되고 반대로 신버전의 소스를 구버전에 호환되는 형식으로 바꾸는데도 이용된다.
  • 원본 코드의 디버깅이나 개발을 쉽게 만들게 하기 위해서 소스코드의 구조는 유지하는 것이 일반적이다.

트랜스파일(transpile)이란, transcompiler에 있는 pile을 쓴 것이다. file이 아니다.

바벨 설치하기

npm install @babel/core @babel/cli
  • @babel/core: 핵심적인 동작이 담겨있는 바벨 코어가 있다.
  • @babel/cli: 커멘드라인 명령어를 지원하기 위한 바벨 CLI도 있다.

바벨 테스트해보기

js파일 작성하기

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

먼저 최신 문법인 화살표 함수를 이용하여 테스트할 js파일을 하나 작성해두었다.

바벨 명령어 입력하기

npx babel custom-babel-test.js

위는 babel 명령어를 통해 테스트용으로 작성했던 js를 트랜스파일해보자.

오잉? 결과가 트랜스파일 하기 전이랑 똑같다. 왜 그럴까? 먼저 바벨의 빌드 단계부터 알아보자.

바벨의 빌드 3단계

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

이전에 명령어를 입력했을 때, 결과가 바벨을 타기 전과 같았으니, 아마 변환 단계에서 아무런 일도 일어나지 않은 것 같다.

바벨 플러그인

바벨은 기본적으로는 위에서도 설명했듯, 소스코드를 받아서 소스코드를 반환한다. 근데 그 과정에서 바벨은 파싱과 출력만 담당하게 되고 변환 작업은 "플러그인"이 처리하게 된다.

바벨의 코드 변환은 플러그인 (혹은 preset)을 설정 파일에 적용함으로써 활성화됩니다. from 바벨 공식문서

바벨 플러그인을 만드는 상세한 방법은 바벨 핸드북에 적혀있다.

커스텀 플러그인과 visitor 자세히 알아보기

custom-babel-plugin.js 파일 생성하기

module.exports = function customBabelPlugin() {
    return {
        // 보통 visitor라는 객체를 만들게 됨
        visitor: {
            Identifier(path) {
                const name = path.node.name;

                // 바벨이 만든 AST 노드 출력
                console.log("Identifier() name:", name);

                // 변환 작업: 코드 문자열을 역순으로 변환
                path.node.name = name.split("").reverse().join("");
            },
        },
    };
};
  • 보통 바벨의 플러그인은 오브젝트 내부에 visitor: 프로퍼티를 반환하는 함수로 시작된다.
  • visitor 프로퍼티 내부에 Identifier 메소드를 구현한다.
  • Identifier가 받을 path에서는 node.name 등 많은 정보를 가져올 수 있다.

바벨 플러그인 visitor 프로퍼티에서 hook 가능한 것들의 목록

바벨은 AST (Abstract Syntax Tree)의 자바스크립트 버전인 ESTree를 사용한다. 바벨에서 사용하는 파서의 코어한 스펙은 전부 여기 공식 스펙 문서에서 확인해볼 수 있다.

바벨 명령어 살펴보고 바벨 플러그인 실행시켜보기

npx babel --help 명령어를 쳐보면 babel에 어떤 명령어들이 있는지 알 수 있다.

여기서 우리는 바벨의 plugin을 사용해보는 것이니까 plugins를 이용해 명령어를 입력해보자.

npx babel custom-babel-test.js --plugins './custom-babel-plugin.js'

실행 결과

실행 결과는 위와 같은데, 이전에 작성했던 js파일의 구문분석 결과로 AST 노드의 토큰 하나하나를 path.node.name으로 가지고 있다.

그리고 각 토큰이 split("").reverse().join("")에 의해 순서가 반대가 되어 있다.

const를 var로 변환하는 플러그인 작성하고 실행해보기

module.exports = function customBabelPlugin() {
    return {
        // 보통 visitor라는 객체를 만들게 됨
        visitor: {
            VariableDeclaration(path) {
                if (path.node.kind === "const") {
                    path.node.kind = "var";
                }
            },
        },
    };
};

위와 같이 작성해주면, 올드한 구형 브라우저에서 호환되지 않는 const를 사용하는 변수를 찾아 var로 변환해 줄 수 있다.

실행 결과

바벨이 제공해주는 플러그인을 사용해보기

이전에 작성해본 customBabelPlugin은 사실 바벨에서 플러그인 형태로 이미 제공해주고 있다.

바벨 플러그인의 babel-plugin-transform-block-scoping 설명 페이지에 가보면 예제에서 letvar로 바꿔주는 것을 확인할 수 있다.

block-scoping 패키지 설치

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

실행해보기

npx babel custom-babel-test.js --plugins @babel/plugin-transform-block-scoping

--plugins 뒤에 설치했던 패키지명을 그대로 입력해주면 된다.

실행결과

실행결과 constvar로 바뀌어있는 것을 확인할 수 있다.

arrow-functions 패키지 설치

arrow-functions는 IE에서 인식하지 못하는 화살표 함수를 일반 자바스크립트 함수로 트랜스컴파일해줄 수 있는 기능을 가진 플러그인이다.

실행해보기

npx babel custom-babel-test.js --plugins @babel/plugin-transform-block-scoping,@babel/plugin-transform-arrow-functions

실행결과

플러그인을 두개 이상 적용하고 싶다면 , 컴마로 이어주면 된다.

결과를 보면, 화살표 함수도 사라져있고 constvar로 바뀐 것을 확인할 수 있다.

strict-mode 설치

strict-mode는 자바스크립트 문법에 엄격 모드를 적용시키는 플러그인이다.

설치 후 실행해보기

npm install @babel/plugin-transform-strict-mode

"use strict"가 추가되었다.

바벨 설정파일에 사용할 플러그인 설정해보기

babel.config.js 파일을 프로젝트 메인에 생성해보자.

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

그냥 바벨 명령어로 트랜스파일 해보기

이전처럼 플러그인 옵션을 안주고 그냥 npx babel custom-babel-test.js를 해도 결과가 플러그인을 적용한 것처럼 잘 나온다.

바벨의 프리셋 개념

실무 프로젝트에서는 바벨의 수많은 플러그인을 사용해야 하는데, 바벨의 플러그인을 하나하나 설치하다가는 시간 소요가 엄청날 것이며, 그루핑하여 관리하기도 쉽지 않을 것이다. 목적에 맞게 여러 개의 플러그인을 모아놓은 것을 "프리셋(preset)"이라고 부른다.

관계로 표현하자면 n개의 plugin이 1개의 preset이 된다.

커스텀 프리셋 만들어보기

custom-babel-preset.js 파일에 아래와 같은 내용을 넣어주었다.

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

babel.config.js의 내용을 다음과 같이 변경했다.

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

플러그인들이 뭉쳐진 프리셋이 잘 적용됐다.

자주 사용되는 프리셋

바벨에서는 목적에 따라 자주 사용되는 몇가지 프리셋이 있다.

  • preset-env: ECMAScript2015+를 변환할 때 사용한다. 바벨7이전에는 babel-reset-es2015와 같이 버전별로 제공되었지만, 현재는 통합되어 사용하기 편리해졌다.
  • preset-flow: flow를 변환한다.
  • preset-react: react를 변환한다.
  • preset-typescript: 타입스크립트를 변환한다.

preset-env 사용해보기

인터넷 익스플로러 지원을 위해 env 프리셋을 사용해보자.

npm install -D @babel/preset-env

babel.config.js에 preset 설정하기

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

결과

실무에서는 이렇게 프리셋을 갖다쓴다.

타겟 브라우저 설정해보기

babel.config.js 작성하기

module.exports = {
    presets: [
        [
            "@babel/preset-env",
            {
                targets: {
                    chrome: "79",
                },
            },
        ],
    ],
};

아까와 다르게 presets 내부 @babel/preset-env 뒤에 targets 가 추가되었다.

이번에는 constarrow-function 모두 그대로 유지됐다. 둘 다 chrome에서 사용하기에 문제 없는 문법이기 때문이다.

babel-preset-env의 공식문서에 가보면 사용할 수 있는 다양한 옵션들이 나타나있다.

chrome 외에도 opera, firefox, safari, ie, ios, android, node, electron 등이 있다.

CAN I USE 닷컴에서 내가 어떤 문법을 어떤 브라우저에서 사용할 수 있는지 볼 수 있다.

타겟브라우저로 ie와 chrome 동시에 설정해보기

module.exports = {
    presets: [
        [
            "@babel/preset-env",
            {
                targets: {
                    chrome: "79",
                    ie: "11",
                },
            },
        ],
    ],
};

이렇게 두 개 이상의 브라우저를 설정할 수도 있다.

ie에 잘 호환되도록 코드가 바뀌었다.

폴리필 이용해보기

Promise 객체 생성해보기

app.js라는 파일을 만들어서 다음과 같은 내용을 추가했다.

new Promise();

Promise는 es6에 있는 객체이다.

결과보기

바벨의 한계

ie에서는 분명 Primise를 지원하지 않는데, new Promise()가 그대로 들어있다. 바벨은 ESCMAScript2015+에서 ECMAScript5 버전으로 변환할 수 있는 것들만 빌드한다. 그렇지 못한 것들은 "폴리필"이라고 부르는 코드 조각들을 추가해서 해결해야 한다.

이전의 ECMAScript2015의 블록 스코핑은 ECMAScript5의 함수 스코핑으로 대체가 가능했고, 화살표 함수도 일반 함수로 대체가 가능했다. 그러나 Promise는 ECMAScript5 버전으로 대체할 수 없다. 다만, ECMAScript5 버전으로 구현할 수는 있다.

구현 참조, core-js promise

env 프리셋으로 폴리필 지정하기

babel-preset-env는 옵션으로 폴리필을 지정할 수 있는 옵션을 제공한다. useBuiltIns는 어떤 방식으로 폴리필을 사용할지 설정하는 옵션이다. "usage", "entry", false 세가지 값을 사용하는데, 기본 값이 false 이므로 폴리필이 동작하지 않았던 것이다. 반면 "usage" 혹은 "entry"를 설정하면 폴리필 패키지 중 core-js를 모듈로 가져온다.

babel.config.js에서 아래와 같이 설정하면 된다.

module.exports = {
    presets: [
        [
            "@babel/preset-env",
            {
                targets: {
                    chrome: "79",
                    ie: "11",
                },
                useBuiltIns: "usage",
                corejs: {
                    version: 3,
                },
            },
        ],
    ],
};

corejs를 사용하려면 npm install core-js@3 명령어로 core-js를 설치해야 한다.

profile
풀스택 웹개발자로 일하고 있는 Jake Seo입니다. 주로 Jake Seo라는 닉네임을 많이 씁니다. 프론트엔드: Javascript, React 백엔드: Spring Framework에 관심이 있습니다.

1개의 댓글

comment-user-thumbnail
2024년 1월 1일

잘읽었습니다 감사합니다!

답글 달기