파크로그
article thumbnail
Published 2022. 7. 21. 20:05
Babel 그리고 Polyfill (core-js) Frontend

 

Babel 은 문법을 바꿔주고, core-js 와 같은 polyfill 로 기능을 추가하여 오래된 브라우저 환경에서 문제없이 동작시키게 도와줍니다.

 

Polyfill 은 구형 브라우저에서 지원하지 않는 기능을 제공하는 코드를 의미합니다. 

ES6의 Promise 같은 객체들은 ES5에 존재하지 않으므로 구문 변환 뿐만 아니라 해당 객체들을 정의하는 코드인 바벨 폴리필(babel polyfill) 을 추가해야 합니다.

 

1. 아주 먼 옛날.. @babel/polyfill

과거에는 @babel/polyfill 패키지를 직접 전역 스코프에 가져오는(import) 방식으로 바벨 폴리필을 추가하여 사용하기도 했습니다.

 

@babel/polyfill 은 내부적으로 core-js/stable  regenerator-runtime 를 포함하고 있는데요,

현재는 @babel/polyfill 은 core-js/stable 을 직접 import 하는 방식을 사용하기 위해서 deprecated 되었습니다.

왜 deprecated 되었나?

Babel 의 버전이 7.4 버전으로 업데이트 하면서 corejs@3 을 지원하게 되었습니다.

 

corejs@3 은 많은 새로운 기능을 포함하여 릴리즈되었고, @babel/plugin-transform-runtime 에 polyfill 을 추가하는 것이 가능해졌습니다. 해당 기능으로 오래된 브라우저의 전역 오염을 방지할 수 있게 되었습니다.

 

@babel/polyfill 은 corejs@2 를 dependency 로 사용하고 있는 Runtime package 였습니다. corejs@2 와 corejs@3 은 서로 공존하여 사용될 수 없고, corejs@3 을 사용하는 과정에서 deprecated 하기로 결정합니다.

 

@babel/polyfill isn't a plugin or preset, but a runtime package: if we added an option to switch between core-js@2 and core-js@3, both the package versions would need to be included in your bundle. For this reason, we decided to deprecate it

자세한 내용은 바벨 7.4 릴리즈 노트에서 확인할 수 있습니다.

// 이전방식: entry 가 되는 파일(전역스코프)에 @babel/polyfill 을 직접 넣음
// src/js/main.js

import "@babel/polyfill";

~~
// 이전방식: webpack 을 사용하는 경우 @babel/polyfill 을 entry 에 추가하여 전역 스코프에 주입
// webpack.config.js

module.exports = {
  // entry files
  entry: ['@babel/polyfill', './src/js/main.js'],
  ...

2. Polyfill 을 직접 추가해보자 (useBuiltIns)

간단하게 babel 설정 파일에서 useBuiltIns 옵션과 corejs 의 옵션을 명시해주면 됩니다.

다만 core-js 는 어느 dependency 에도 포함되어 있지 않기 때문에 별도로 core-js 를 설치해주어야 합니다.

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "> 0.25%, not dead",
        "useBuiltIns": "entry",
        "corejs": { "version": 3, "proposals": true }
      }
    ]
  ]
}

useBuiltIns options

저는 먼저 entry 라고 적었는데요, 이 옵션의 값으로 entry, usage, fasle 세 값이 가능합니다.

preset-env 를 사용할 때 polyfill 을 사용하지 않을 수도 있으니 default 는 false 입니다. 그럼 저희는 polyfill 을 사용하기위해 entry 혹은 usage 를 선택해야 합니다.

 

이전에 @babel/polyfill 을 사용할 때에 @babel/polyfill 을 직접 전역에 import 해야했습니다. 그와 비슷한 방법으로 쓰여지는 것이 entry 옵션입니다.

 

entry 하는 js 파일의 가장 상위에 polyfill 할 모듈을 import 합니다.

core-js/stableregenerator-runtime/runtime

바로 import 하지않고 또 단락이 넘어온 이유는 @babel/polyfill 에서 넘어왔을 당시에는 core-js/stable 을 import 하였고 generator 문법이나 async function 을 사용하는 경우 regenerator-runtime/runtime 모듈도 import 해주어 사용하면 되었습니다.

 

import 'core-js/stable';
import 'regenerator-runtime/runtime';

const delay = (ms) => {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
};

const init = async () => {
  await delay(1000);
  console.log('hi');
};

init();

공식문서와 블로그글들을 비교해가면서 읽어 보고 있는데, 조금 다른 부분이 있었습니다.

 

공식문서에는 단지 core-js 만을 import 하여 사용하고 있었습니다. @babel/core 이나 @babel/plugin-transform-regenerator 의 버전이 7.18 이하라면 regenerator-runtime/runtime 패키지를 따로 import 해야한다고 합니다.

예전 babel 7.17 버전을 사용했던 boilerplate 를 가져와서 실험을 해보았는데, import 'core-js/stable' 만 했을 때에는 해당 오류가 발생하였습니다.

import "core-js/stable";

const delay = (ms) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(true);
    }, ms);
  });
};

const init = async () => {
  await delay(1000);
  console.log("1초뒤 hi");
};

init();
console.log("바로 hi");

왼쪽은 ie11, 오른쪽은 Chrome

 

regenerator 관련 모듈을 import 해주고 나니 해당 오류는 고쳐졌습니다.

import "core-js/stable";
// 추가
import "regenerator-runtime/runtime";

const delay = (ms) => {
 ...
};

...

 

2022년 5월 릴리즈된 babel 7.18 버전부터 regenerator-runtime 은 @babel/plugin-transform-runtime 이 사용되지 않을 때 자동으로 inline 된다고 합니다.

 

그렇기 때문에 현재 사용되고있는 babel@7.18 부터는 useBuiltIns 옵션으로 entry 를 사용할 시 import 'core-js' 혹은 import 'core-js/stable' 을 사용하면 됩니다.

 

core-js vs core-js/stable ?

core-js 와 core-js/stable 의 차이는 esnext 를 사용할 것인지 여부였습니다.

babel 에서는 esnext 에서 stage0~3 은 포함하지 않는 것으로 알고있어서, stage4 (finished) 만 포함이 되었을 것으로 생각이 되네요.

core-js 를 import 했을 때와 core-js/stable 을 했을 때 diff 를 확인해보았다.

 

useBuiltIns usage vs entry

entry 는 이제 전역으로 polyfill 을 설정한다는 것을 이해했습니다. 그렇다면 usage 는 무엇일까요?

usage 는 내가 사용하고 있는 메서드의 polyfill 이 필요한 경우, 실제 사용한 폴리필만을 삽입합니다. 

 

entry point file 에서 모두 import 를 한 이후 사용하는게 아닌, 만약 각각의 파일에서 (a.js, b.js) 사용된다면 각각의 파일에서 import 됩니다.

 

그렇다면 무조건 usage 쓰면 import 되는게 더 적어져서 좋은게 아니냐? 라고 생각할 수 있고 저도 그렇게 생각했습니다.

 

그런데 만약 아래와 같은 상황이 있다고 가정합시다.

 

만약 사용자에게서 Array 의 method 를 입력받아, 해당 method 의 length 를 확인하고 싶어한다면?

(실제론 method 의 length 가 필요한 상황은 없겠지만 이와 같은 상황이 있다고 가정하겠습니다)

코드는 아래와 같습니다.

const name = prompt("What array method do you want to know the length of?");
console.log("It's " + [][name].length);

prompt 가 나오고 Array.prototyle 의 메서드 를 입력하면 해당 메서드의 length 를 출력해주는 코드입니다.

만약 사용자가 flatMap 과 같은 메서드 명을 입력하면 동작하지 않을겁니다.

코드상에 flatMap 이 존재하지 않았기 때문에 Babel 이 해당 메서드를 미리 polyfill 할 수 없었던 것입니다.

 

babel 의 개발자인 nicolo-ribaudo 는 아래와 같이 추천하고 있습니다.

My recommendation is to use useBuiltIns: "usage" if you are ensuring that your code works in old browsers; if you just "trust that it works, without testing it" is better to use useBuiltIns: "entry".

 

 

3. 전역오염을 막기위한 core-js/pure 

이제서야 이 글을 작성하게 된 계기가 되었던 키워드로 왔는데요,

 

사실 이 글은 core-js 를 설치하지 않고 사용하려고 했다가, 왜 설치가 안됐지? dependency 에 없나? 하고 찾아보다 core-js/pure 라는 것을 발견하고, 해당 모듈을 사용할 방법이 없나 에서 시작되었습니다.

 

core-js/pure 는 전역 스코프를 오염시키지 않는 환경을 만들기 위한 core-js 인데요, @babel/transform-runtime 과 함께 사용합니다. @babel/plugin-transform-runtime 는 어느 dependency 에도 속하지 않기 때문에 다운로드하고 사용해주세요.

{
    "presets": [["@babel/preset-env",{"targets": {"ie": 11}}]],
    "plugins": [["@babel/plugin-transform-runtime",{"corejs":3}]]
}

.

해당 방법의 한 가지 아쉬운 점은 plugin-transform-runtime 플러그인의 경우 preset-env와 다르게 targets 옵션을 지원하지 않습니다.

targets 의 브라우저에 맞춰서 필요한 polyfill 만 추가 하는 최적화할 수 없다는 것 입니다.

 

https://github.com/babel/babel/issues/10133

 

+) 해당 방법을 채택하여 사용중이라면 경우에따라 추가로 작업해야할 게 있습니다.

추가적으로 webpack 에서 성능을 위해 node_modules 를 exclude 폴더로 설정했다면, ES6 syntax 를 사용하는 module 들( 예 - axios, @redux-saga, redux-logger 등등) 의 polyfill 이 설정되지 않으므로 babel-loader 를 사용하는 부분에 있어서, exclude 를 해당 모듈을 제외한 것으로 작성하여 사용하도록 합니다.

 

module.exports = {
  ...,
module: {
rules: [{
    test: /\.(js|jsx)$/,
    exclude: /node_modules\/(?!(axios|@redux-saga|redux-logger))/,
    use: {
      loader: 'babel-loader'          
    },
  }]}}

4. Babel-polyfills (진행중)

babel 의 polyfill 을 어떻게 추가할지에 대해서 History 와 함께 살펴보았는데, 위에서 언급한 것 처럼 몇가지 문제가 있었습니다. 

 

1. @babel/preset-env 의 targets 옵션 처럼 @babel/plugin-transform-runtime 의 targets 를 지정할 수 없음

2. core-js 가 강요되어진다.

 

위 두 가지 문제를 해결하기 위해 새로운 패키지를 만든 것이 babel-polyfills 입니다.

 

아직 0.x 버전이라 공식적인 버전도 아니고, pure 를 테스트 해보았을 때 오류도 있고 해서 지금 당장에 쓰일 수 있는 패키지는 아닌 듯 합니다.

 

기존에 쓰이던 babel 설정에서 targets 를 바깥으로 빼고, 사용하고자 하는 plugin 을 설정하여 사용하면 됩니다.

여기서 지원하는 Polyfill 은 core-js@2 , core-js@3, es-shims, regenerator-runtime 등이 있는데요, 

 

es-shims 를 제외하고 @babel/plugin-transform-runtime 와 @babel/preset-env 의 dependency 로 존재합니다.

때문에 별도의 plugins 에 어떤 polyfill 을 사용할 것인지 명시만 해주면 되었습니다. 

// migration.md 참고 
// https://github.com/babel/babel-polyfills/blob/main/docs/migration.md

// 이전에 쓰인 usage 설정

// {
//   "presets": [
//     ["@babel/preset-env", {
//       "targets": "ie 11",
//       "useBuiltIns": "usage",
//       "corejs": 3
//     }]
//   ]
// }

// Babel-polyfills 의 migration 

{
  "targets": { "ie": 11 },
  "presets": ["@babel/preset-env"],
  "plugins": [
    [
      "polyfill-corejs3",
      {
        "method": "usage-global"
      }
    ]
  ]
}

 

profile

파크로그

@파크park

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!