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/stable , regenerator-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");
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) 만 포함이 되었을 것으로 생각이 되네요.
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 만 추가 하는 최적화할 수 없다는 것 입니다.
+) 해당 방법을 채택하여 사용중이라면 경우에따라 추가로 작업해야할 게 있습니다.
추가적으로 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"
}
]
]
}