본문으로 바로가기

프론트엔드 개발환경의 이해 : Babel

category Frontend 2021. 4. 1. 00:21

바벨

프론트엔드의 코드들은 각각의 브라우저 상황에 맡게 돌아가기 폴리필등의 이유 때문에 일관적이지가 못하다

진입장벽을 높히고 협업을 힘들게 하기도 하는데 이런 단점을 보완하기 위해 나온 것이 바벨.

바벨은 ECMAScript2015+로 작성한 코드를 모든 브라우저에서 일관적이게 동작하도록 호환성을 지켜준다

바벨의 동작

바벨은 3단계를 거쳐서 빌드한다

  1. 파싱
    => 코드들을 토큰으로 나누어서 파싱하고

  2. 변환
    => 파싱한 코드들을 변환해서

  3. 출력
    => 출력한다


마이크로소프트의 인터넷 익스플로러는 자바스크립트 ES6의 문법을 지원하지 않는다

그래서 constlet arrow function을 사용할 수 없는데

바벨을 사용하면 동작할 수 있게 트랜스파일링해준다

우선 IE에서 돌아가지 않을만한 코드 하나를 작성해보자

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

alert 함수는 메세지를 파라미터로 받아서 alert으로 띄워주는 함수다

터미널에 바벨을 실행해보자

sample % npx babel app.js // 바벨 실행

const alert = msg => window.alert('msg');  // 출력 결과물

왜 빌드 결과물이 전과 똑같을까?

플러그인

바벨은 사실 파싱하고 출력만을 담당하고

플러그인들이 변환을 담당한다 현재 적용된 플러그인이 하나도 없기 때문에 파싱되고 난 후에 아무것도 변환하지 않고 출력된 것

커스텀 플러그인

여태까지와 같이 플러그인을 만들어보면서 동작 원리를 이해해보자

module.exports = function myBabelPlugin(){
    return{
        visitor:{
            Identifier(path){
                const name = path.node.name;

                console.log('Identifier() name',name);
            }
        }
    }
}

바벨의 플러그인은 vistor 객체를 반환해야한다

path.node.name로 파싱된 결과물들에 접근할 수 있다

위의 플러그인은 파싱된 결과물들을 하나하나씩 콘솔로그에 띄우는 일만 하는 플러그인인데

cli로 바벨을 실행할 때 옵션을 주는 법은 babel '파일명' --plguins '플러그인 경로' 이다

    sample % npx babel app.js --plugins './my-babel-plugin.js' 
Identifier() name alert
Identifier() name msg
Identifier() name window
Identifier() name alert
const alert = msg => window.alert('msg');

파싱된 하나하나의 결과물에 접근해서 로그를 띄우는 것을 볼 수 있다

가령 아래와 같이 해주면 코드의 좌우를 뒤집는 플러그인이 만들어지게 되는 것

  Identifier(path){
    const name = path.node.name;

    path.node.name = name.split('').reverse().join('');

위에서 Identifier를 사용할 때 이상한 것을 느꼈는데 바로 const가 파싱되어 들어오지 않았다는 것이다

VariableDeclaration라는 함수를 사용하면 const까지 파싱되어 접근할 수 있다

        visitor:{
            VariableDeclaration(path){
                console.log('VariableDeclaration() kind', path.node.kind);

                if(path.node.kind === 'const'){
                    path.node.kind = 'var';
                }
            }
        }

const를 잡아와서 var로 바꿔주는 플러그인이 만들어졌다

sample % npx babel app.js --plugins './my-babel-plugin.js'
VariableDeclaration() kind const
var alert = msg => window.alert('msg');

진짜 플러그인

당연하게도 이러한 플러그인들은 수요가 많기 때문에 직접 작성해서 사용할 필요가 없다

바벨에 이미 존재하고 있는 플러그인들을 가져와서 사용해보자

@babel/plugin-transform-block-scoping : const를 바꿔주는 플러그인

@babel/plugin-transform-arrow-functions : arrow funtion을 일반 함수로 바꿔주는 플러그인

각각 설치한 후 플러그인을 적용해서 바벨을 돌려보자

sample % npx babel app.js \
>   --plugins @babel/plugin-transform-block-scoping \
>   --plugins @babel/plugin-transform-arrow-functions
var alert = function (msg) {
  return window.alert('msg');
};

babel.config.js 설정하기

매번 이렇게 명령어로 길게길게 적어서 실행할 순 없을 것 같다

바벨도 웹팩처럼 설정 파일을 제공한다

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

이렇게 설정해두면 이제 babel만 실행시켜도 플러그인이 다 적용된 상태로 빌드가 된다

preset

이런 플러그인들을 모아놓은 것을 프리셋이라고 하는데 용도에 맞는 플러그인들을 모아놓은 꾸러미..? 라고 생각하면 될 것 같다

그래서 실제로 사용할 때는 만들어진 프리셋을 가져다가 쓰는 경우가 많다

어떤식으로 작동하는지 보기위해 위의 3가지의 플러그인을 가지고 프리셋을 만들어보자

// my-babel-preset.js
module.exports = function myBabelPreset(){
    return{
        plugins: [
            "@babel/plugin-transform-block-scoping",
            "@babel/plugin-transform-arrow-functions",
            "@babel/plugin-transform-strict-mode",
        ]
    }
}
// babel.config.js
module.exports = {
    presets: [
        './my-babel-preset.js'
    ]
}

실제 프리셋

커스텀 프리셋을 만들고 사용해봤으니

실제로 바벨에서 제공하는 프리셋을 사용해 보자

자주 사용하는 프리셋은

preset-env, preset-flow, preset-react, preset-typescript 정도가 있는데

flow, react, typescript는 이름에서 알 수 있듯이 각각의 언어를 위한, env는 ECMAScript2015+를 사용할 때 쓰는 프리셋이다

npm i @babel/preset-env로 설치하고

babel.config.js에서 프리셋을 수정한 뒤 사용해주자

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

타겟 브라우저

바벨의 역할은 다양한 브라우저에서 최신 자바스크립트가 작동하도록 변환시켜주는 것이라고 했다

하지만 프로젝트가 서비스할 브라우저가 크롬 뿐이라면?

IE가 지원하지 않고 크롬에서 지원하는 기능을 굳이 IE까지 호환되게끔 변환해줄 필요는 없다

그럴 때 사용하는 것이 babel의 타겟 브라우저 옵션이다

module.exports = {
    presets: [
        ['@babel/preset-env',{
            targets: {
                chrome: '89'
            }
        }]
    ],

}

이제 다시 babel을 돌려서 app.js를 빌드해보자

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

크롬에서는 const와 arrow function을 전부 호환하기 때문에 변환할 필요가 없기 때문에 그대로 출력하고 있다

targets ie : 11 옵션을 넣는다면 const는 var로 arrow function은 일반 함수로 바뀌게 된다

...
    presets: [
        ['@babel/preset-env',{
            targets: {
                chrome: '89',
                  ie: '11',
            }
        }]
...

폴리필

app.js의 내용을 바꿔보자

//app.js
new Promise();

이 코드를 바벨로 빌드하면

new Promise();

변환되지 않고 똑같은 코드가 나오게된다

그런데 이것을 IE환경에서 돌리면 Promise를 지원하지 않아서 에러가 발생한다

어찌된 일일까?

preset-env가 설정된 바벨은 ECMASCript2015+로 작성된 코드들을 ECMAScript5으로 변환이 가능할 때만 변환해준다

const는 var로 arrow function은 일반 함수로,

하지만 ECMAScript5에서는 Promise를 지원하지 않기 때문에 Promise는 변환할 수가 없다

이럴 때 필요한게 폴리필이다

ECMAScript5가 지원하지 않는 기능을 코드를 추가해서 구현하는 것

타겟 브라우저를 설정한 것 처럼 옵션 객체를 설정해보자

    useBuiltIns: 'usage',
      corejs: {
        version:2,
      }

useBuiltIns는 폴리필을 어떻게 적용할지 설정하는 옵션인데 명시하지 않는다면 기본값인 false로 설정된다

그렇기 때문에 빌드할 때 폴리필을 하지 않았던 것

그 아래에 폴리필시에 사용할 패키지 명을 적어주면 빌드시에 corejs를 가져와서 폴리필한다

이제 다시 빌드 결과물을 보자


require("core-js/modules/es6.object.to-string.js");

require("core-js/modules/es6.promise.js");

new Promise();

이전과 다르게 corejs 패키지를 임포트하는 구문이 추가된 것을 볼 수 있다