oungo

@babel/polyfill 문제 해결 방법 - @babel/runtime, @babel/plugin-transform-runtime 사용

멀쩡하던 사이트에서 갑자기 babel-polyfill 관련 에러가 떴다. polyfill 관련 에러가 왜 나는 건지 알아보고, @babel/runtime, @babel/plugin-transform-runtime을 사용해서 polyfill 이슈를 해결하는 방법에 대해 알아보자.

Babel이란?

바벨은 자바스크립트 컴파일러로 ECMAScript 2015 이상의 자바스크립트 코드를 오래된 브라우저나 환경에서도 동작할 수 있도록 코드를 이전 버전으로 바꿔주는 도구입니다.

babel 출처 What is Babel?

이런 식으로 자바스크립트 최신 문법을 이전 문법에 맞게 변환해줍니다

// Babel Input: ES2015 arrow function
[1, 2, 3].map(n => n + 1);

// Babel Output: ES5 equivalent
[1, 2, 3].map(function (n) {
  return n + 1;
});

babel-polyfill

바벨은 최신 자바스크립트 코드를 internet explorer 같은 오래된 브라우저에서도 정상적으로 동작할 수 있도록 해줍니다. 하지만 Promise, Set, Map 같은 객체들은 ES5에도 정의되어 있지 않기 때문에 해당 객체들을 정의하고, 이전 버전에 맞게 코드가 작성되어있어야 합니다.

babel-polyfill은 regenerator runtime과 core-js를 import하는 역할을 하는데 이를 통해 위와 같은 자바스크립트 최신 객체들을 사용할 수 있습니다.

하지만 babel-polyfill에는 문제가 있습니다

  1. 모든 polyfill을 가져오기 때문에 번들 사이즈에 영향을 줄 수 있습니다

  2. 전역 스코프를 오염시킨다

  3. babel-polyfill은 프로젝트 내에서 딱 한 번만 import 되어야 하는데 두 개 이상의 polyfill이 import되면 아래와 같은 에러가 발생합니다 babel

최근에 3번 문제로 사이트에서 문제가 발생했습니다. 프로젝트에서는 babel-polyfill 패키지를 사용하고 있었습니다. 4년 전에 추가된 후로 폴리필 패키지는 변경된 적이 없습니다. 근데 갑자기 폴리필 문제가 발생한 건데 원인은 외부에서 다운받는 스크립트였습니다. 외부에서 다운받는 트래킹 관련 스크립트가 있었는데 갑자기 해당 스크립트에서 babel-polyfill을 중복으로 import해서 발생한 이슈였습니다.

@babel/runtime, @babel/plugin-transform-runtime

위 문제를 해결하려면 babel-polyfill을 제거하고, @babel/runtime과 @babel/plugin-transform-runtime 패키지를 사용하면 됩니다.

바벨은 트랜스파일하는 과정에서 폴리필이 필요한 코드를 내부 helper 함수로 치환합니다.

아래와 같은 코드가 있으면

class Circle {}

이런 식으로 _classCallCheck라는 helper 함수를 정의하고 이를 사용하는 방식으로 코드가 변환됩니다

function _classCallCheck(instance, Constructor) {
  //...
}

var Circle = function Circle() {
  _classCallCheck(this, Circle);
};

이는 class를 사용하는 모든 파일에 _classCallCheck를 정의하는 코드가 매번 필요하다는 걸 의미합니다.

이때 필요한 게 @babel/plugin-transform-runtime 패키지입니다. @babel/plugin-transform-runtime을 사용하면

var _classCallCheck = require('@babel/runtime/helpers/classCallCheck');

var Circle = function Circle() {
  _classCallCheck(this, Circle);
};

이런 식으로 @babel/runtime에 있는 헬퍼 함수를 참조하게 되어 불필요한 코드의 중복을 없앨 수 있습니다.

다시 정리하면 @babel/plugin-transform-runtime을 통해 매번 필요한 헬퍼 함수를 정의하지 않고, 각종 헬퍼 함수들을 포함하고 있는 패키지인 @babel/runtime을 참조하는 방식으로 사용할 수 있습니다.

주의할 점

주의할 점이 한 가지 있는데 패키지 내부에서 Promise와 같은 최신 객체를 사용하는 경우 이런 식으로 webpack에서 babel-loader를 사용할 때, node_modules에 있는 패키지까지 트랜스파일 범위에 포함되도록 해줘야 합니다.

exclude: [/node_modules\/(?!(axios)\/).*/];

마무리

처음엔 polyfill 관련 에러가 뜬금없이 발생해서 조금 헤맸었는데 결과적으로 babel-polyfill을 제거하고, @babel/runtime과 @babel/plugin-transform-runtime을 사용함으로써 문제를 해결함과 동시에 독립적인 코드 환경을 만들고, 불필요한 코드의 중복을 없앨 수 있었습니다.

그동안 프로젝트를 진행하면서 webpack이나 babel같이 설정관련된 파일들은 항상 갖춰져 있는 상태로 작업했었던 것 같습니다. 이번에 기회가 되어서 설정 관련 파일들을 만져봤는데 처음에 파악하는 데 시간이 걸렸지만 익숙하지 않던 새로운 부분을 알아갈 수 있어서 좋은 경험이었던 것 같습니다.