본문 바로가기

웹 프론트엔드

컴파일과 폴리필의 차이점 분석 (babel, polyfill)

서언

웹 생태계에서 JavaScript는 브라우저와 뗄 수 없는 관계입니다. JavaScript는 매번 새로운 버전을 출시합니다. 그러면 브라우저는 최신 문법을 이해할 수 없게 됩니다. 만일 브라우저가 빠르게 대응한다고 하더라도, 사용자가 최신 브라우저를 사용할 것이라는 보장이 없습니다. 그러므로 브라우저가 최신 문법을 이해하도록 만들기 위해서 개발자가 노력을 가해야 합니다. 이때 사용할 수 있는 카드가 Compile 입니다. Compile의 중요성은 널리 알려진 것 같습니다. 하지만 Compile만으로 해결할 수 없습니다. Compile 만큼 중요한 것이 Polyfill 입니다. 이 글은 Compile과 Polyfill의 차이점과 그 역할에 초점을 맞춥니다.

컴파일이란?

ES6+ 자바스크립트 코드를 ES5 이하로 변환해주는 것을 트랜스파일이라고 합니다. Source-to-source compile을 transpile이라고 부릅니다. 그렇기 때문에 트랜스파일을 컴파일이라고 부르기도 합니다. 트랜스파일을 가능하게 해주는 자바스크립트 라이브러리가 바로 바벨입니다. 바벨 사이트에 들어가보면 다음과 같은 문구를 볼 수 있습니다.

https://babeljs.io/

“Babel is a JavaScript compiler.”

앞으로 컴파일/트랜스파일을 컴파일로 통칭하겠습니다.
ES6+ 코드를 ES5로 변환한다고 하는데 그러면 모든 ES6+ 코드가 ES5로 변환될까요?
다시 말해 Promise, async, Arrow function, class, Set/Map 등의 문법이 모두 바벨에 의해 컴파일 될까요?

아닙니다.
어떤 것은 바벨에 의해 컴파일 되고, 어떤 것은 컴파일 될 수 없습니다. 예시를 보겠습니다. 아래 코드가 컴파일 되면 어떻게 될까요?

const arrowFunc = () => {}

const aPromise = new Promise();

const spreadOperator = [...[1,2,3], ...[4,5,6]]

const aSet = new Set(1,2,3)

const includesMethod = [1,2,3].includes('1');

다음과 같습니다.

var arrowFunc = function arrowFunc() {};

var aPromise = new Promise();

var spreadOperator = [1, 2, 3].concat([4, 5, 6]);

var aSet = new Set(1, 2, 3);

var includesMethod = [1, 2, 3].includes('1');

즉, 어떤 ES6+ 문법은 정상적으로 바벨에 의해 컴파일 되었고, 어떤 문법은 컴파일되지 못 했습니다.

그렇다면 ES5 이하를 지원하는 브라우저에서 Promise나 Set, Array.prototype.includes 를 읽어낼 수 있을까요?

읽을 수 없겠죠. 이때 필요한 것이 바로 폴리필 입니다.
폴리필에 대해서 알아본 후, 어떤 ES6+ 문법이 컴파일되고, 어떤 것은 컴파일 되지 않아 폴리필이 필요한지 알아보겠습니다.

Polyfill(폴리필)이란?

자바스크립트를 공부하다보면, 폴리필이라는 말을 많이 듣습니다. 하지만, 오랫동안 자바스크립트, React를 공부해도 실제로 폴리필이 어떤 것이며, 어떻게 우리 프로젝트에 적용되고 있는지 깨닫지 못 하고 있을 수 있습니다.

mdn은 폴리필을 다음과 같이 정의합니다.
“polyfill은 이전 브라우저에서 기본적으로 지원하지 않는 최신 기능을 제공하는 데 필요한 코드입니다.”

바벨과 어떤 차이점이 있는지 아시겠나요?
바벨은 최신 ES6+ 문법을 ES5로 바꾸어주는 것이고, polyfill은 브라우저가 이해할 수 없는 코드에 대하여, 이해할 수 있는 코드 소스를 제공하는 것입니다.

가령, class는 ES6 코드이고, 브라우저를 이해시키기 위해 바벨에 의해 ES5 코드로 변환됩니다. 하지만 Array.prototype.includes는 ES6 코드이지만, 바벨에 의해 변환되지 않습니다. 이런 코드를 브라우저가 이해하기 위해서는 폴리필이 필요한 것입니다.

Array.prototype.includes의 폴리필을 한 번 찾아보겠습니다. mdn에 들어가면 찾아볼 수 있습니다.
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/includes#폴리필

if (!Array.prototype.includes) {
  Object.defineProperty(Array.prototype, 'includes', {
    value: function (searchElement, fromIndex) {
      if (this == null) {
        throw new TypeError('"this" is null or not defined');
      }

      var o = Object(this);

      var len = o.length >>> 0;

      if (len === 0) {
        return false;
      }

      var n = fromIndex | 0;

      var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);

      function sameValueZero(x, y) {
        return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));
      }

      while (k < len) {
        if (sameValueZero(o[k], searchElement)) {
          return true;
        }
        k++;
      }

      return false;
    }
  });
}

구체적으로 코드를 살펴볼 필요는 없습니다. 알아야할 지식은 includes polyfill을 추기하면, includes 메소드가 Array의 prototype에 추가되어 정상적으로 includes 메소드를 사용할 수 있다는 것입니다.

이는 core-js 를 직접 설정하거나, @babel/preset-env 에서 자동으로 폴리필을 삽입하도록 만들 수 있습니다.

무엇을 컴파일하는가? 무엇을 폴리필하는가?

다음의 경우 컴파일될 수 없고, 폴리필이 필요합니다.

 

  • ES5의 global namespace(window)에 존재하지 않는 것은 컴파일 하지 못 한다.
    • 새로운 객체 (Promise, IntersectionObserver, Set, Map …)
    • 기존 객체의 새로운 메서드 (Array.prototype.includes, Object.entries …)
    • 새로운 함수 (fetch)

 

바벨이 window.Object, window.Array와 같이 전역 공간에 생성되는 객체가 아니거나, 기존에 존재하지 않는 메서드를 만들어줄 수는 없습니다.

 

자바스크립트에서 전역공간이란 무엇일까요? 바로 전역 객체를 의미합니다.

전역 객체란, 환경에 따라 window, self, this, frames, global, globalThis(ES11에서 통일된 명칭) 등이라고 불리며, 자바스크립트 코드가 실행되기 이전에 생성되는 최상위 객체를 의미합니다. 여기에는 표준 빌트인 객체(Object, String, Number, Array...), 호스트 객체(Web API...), var 키워드로 선언된 전역 변수(NaN, Infinity...), 전역 함수(parseInt, isNaN) 등이 포함됩니다.

 

정리하자면, ES5 환경의 전역 객체에 존재하지 않는 객체, 메서드, 함수는 바벨이 컴파일해줄 수 가 없고 폴리필이 필요합니다.

그리고 다음의 경우 컴파일 가능합니다.

 

  • 새로운 “문법"인 경우
    • const, let
    • spread operator
    • arrow function
    • class
    • destructuring

 

새로운 객체, 메서드, 함수가 아니라 문법의 경우 바벨이 컴파일 합니다.

https://github.com/zloirock/core-js#features
여기에서 어떤 것이 polyfill 대상인지 확인할 수 있습니다. 나머지는 모두 compile 대상입니다. 하지만 이를 암기할 필요는 없습니다.

https://babeljs.io/repl playground에서 다음과 같이 설정하고 core-js polyfill이 추가되는지 확인하여 컴파일/폴리필 여부를 알 수 있습니다.

폴리필 소스가 궁금하면, 해당 문법을 mdn에 검색하여 polyfill 코드를 확인합니다.

마치며

브라우저, 사용자 환경에 따라서 서비스의 지원 여부가 달라져서는 안 됩니다. 하위 브라우저를 충분히 고려해서 개발해야하고, 그러기 위해서는 바벨과 폴리필에 대한 이해는 필수입니다. 둘의 차이점을 명확히 인지하고 환경 설정을 해야합니다. 이 글이 도움이 되길 바랍니다.


작성자 : https://github.com/euijinkk

참고자료

Polyfill - 용어 사전 | MDN
https://ui.dev/compiling-polyfills
Polyfills: everything you ever wanted to know, or maybe a bit less
You don't know polyfill
What you don't know about BabelJS preset-env | PerimeterX
core-js/2019-03-19-core-js-3-babel-and-a-look-into-the-future.md at master · zloirock/core-js