들어가면서
브라우저 호환성 문제를 해결하기 위한 폴리필(polyfill) 개념과 실제 사용 방법을 조사하였습니다. React와 IE11 환경에서 동작하기 위한 폴리필을 중점적으로 다룹니다.
Polyfill 개념
기본적으로 지원하지 않는 이전 브라우저에서 최신 기능을 제공하는 데 필요한 코드 (JavaScript) ”mdn”
자바스크립트는 끊임없이 발전되어왔기 때문에 명세도 계속 변화되어왔습니다.
이 과정에서 모든 브라우저는 발전된 스크립트 문법을 제공하지 않을 수 있기 때문에 브라우저 호환성 문제를 해결하기 위해 일종의 코드 조각을 추가하는 것을 polyfill
이라고 합니다.
polyfill을 지원하는 대표적인 라이브러리는 core-js
와 polyfill.io
가 있습니다.
어떻게 사용되는지 한번 소개드리도록 하겠습니다.
Polyfill.io
User-Agent
헤더를 통해 브라우저를 파악하고, 스크립트 실행을 위한 적절한 코드를 제공합니다.
polyfill.io에서는 필요한 기능들에 대한 polyfill을 선택하면, CDN 스크립트를 제공하여 필요한 페이지에서 로드하여 사용할 수 있습니다.
document.addEventListner('DOMContentLoaded', function () {
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then((response) => response.json())
.then((json) => console.log(json));
});
이 코드는 ie에서 정상적으로 실행이 어렵습니다. (화살표 함수, fetch)
ie에서도 정상적으로 최신 문법을 실행시키도록 하려면 별도의 polyfill을 추가시키면 됩니다.
polyfill.io
를 활용해 해결하는 방법은 원하는 polyfill이 필요한 페이지에 CDN 스크립트를 삽입하면 됩니다.
<script src="https://polyfill.io/v3/polyfill.min.js?features=es2016%2Cfetch"></script>
<script src="app.js"></script>
polyfill.io
에서 제공된 스크립트를 살펴보면, 해당 브라우저에서 지원되지 않는 문법을 자체적으로 사용 가능한 문법으로 감싸주어 사용할 수 있도록 지원해주고 있습니다.
...
//fetch를 사용할 수 있도록 XMLHttpRequest을 활용해 래핑한 함수를 제공한다.
function fetch(input, init) {
return new Promise(function(resolve, reject) {
var request = new Request(input, init);
if (request.signal && request.signal.aborted) {
return reject(new exports.DOMException('Aborted', 'AbortError'))
}
var xhr = new XMLHttpRequest();
function abortXhr() {
xhr.abort();
}
xhr.onload = function() {
var options = {
status: xhr.status,
statusText: xhr.statusText,
headers: parseHeaders(xhr.getAllResponseHeaders() || '')
};
options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL');
var body = 'response' in xhr ? xhr.response : xhr.responseText;
setTimeout(function() {
resolve(new Response(body, options));
}, 0);
};
..
...
}
global.fetch = fetch;
('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
(fetch를 통해 성공적으로 데이터를 가져온 모습)
핵심 기능
Polyfill.io는 URL 파라미터를 통해 필요한 상세 옵션을 전달할 수 있습니다.
- feature
필요한 기능만 선택적으로 요청할 수 있습니다.https://polyfill.io/v3/polyfill.min.js?features=Array.from&CArray.isArray&fetch
- falgs
필요한 폴리필을 모든 브라우저에서 요청하려면 flags에always
매개변수를 전달하면 됩니다.gated
매개변수를 전달하면 제공받은 브라우저에서 동작하는지 확인하고 필요한 경우에만 폴리필을 제공합니다.https://polyfill.io/v3/polyfill.js?features=es5,es6,es7&flags=gated
- unknown
폴리필을 지원하지 않는 브라우저에서는 기능을 내려줄지 결정해야 합니다.ignore
으로 설정하면 지원하지 않는 브라우저에서 폴리필을 제공하지 않고,polyfill
을 전달하면 폴리필 코드를 제공합니다.https://polyfill.io/v3/polyfill.min.js?features=fetch&unknown=ignore
Core.js
Core.js는 babel
환경과의 통합이 잘 되어있어 npm 개발 환경에서 많이 사용하는 polyfill 라이브러리입니다.
Babel이 수행하는 역할에 대해 간단히 살펴보겠습니다.
Babel의 개념
Babel은 ES2015 이상의 문법들을 브라우저에서 호환될 수 있도록 변환해주는 도구입니다. 변환 대상의 범위는 별도의 설정 파일을 이용하여 상세하게 지정할 수 있습니다.
Polyfill
과 역할은 유사하지만 차이점은 분명합니다. polyfill은 최신 문법에 대해 지원하지 않는 하위 브라우저를 위한 추가적인 기능 제공을 수행하며, Babel은 ES2015 이상의 문법들을 하위 버전으로 변환해주는 기능을 수행하는데 기능에 따라 변환하지 못 하는 것들도 존재할 수 있습니다.
예를 들면 다음과 같습니다.
- 새로운 전역 Window에 추가된 객체 (Promise, Map, Set)
- 새로운 메서드 (Array.from, Array.include)
이렇게 Babel이 제공해주지 못하는 문법을 사용하기 위해서는 별도의 polyfill
을 추가해주어야 합니다.
Babel을 활용한 core-js 구성하기
먼저 webpack
을 구성합니다. polyfill 수행에 핵심적인 파일은 Babel이기 때문에 webpack에 대해서는 깊게 다루지는 않겠습니다.
React
다음은 웹팩과 바벨을 통한 번들링 및 트랜스파일링이 잘 되는지 확인하기 위해 간단한 리액트 코드를 작성합니다.
//📂src/App.jsx
import React from 'react';
import { useEffect, useState } from 'react';
export const App = () => {
const names = ['jiny', 'island'];
const [posts, setPosts] = useState(null);
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(function (response) {
return response.json();
})
.then(function (json) {
console.log(console.log(json));
setPosts(json);
})
.catch(function () {
alert('assda');
});
console.log([1, 2, 3, 4].includes('3'));
}, []);
return (
<div>
<h2>Posts Fetch</h2>
<h3>
title: <span>{posts?.title}</span>
</h3>
<p>{posts?.body}</p>
<ul>
{names.map((item) => {
return <li>{item}</li>;
})}
</ul>
</div>
);
};
//📂src/index.js
import { render } from 'react-dom';
import { App } from './App';
render(<App />, document.querySelector('#root'));
위의 코드를 살펴보면 ES2015 이상의 const, let, Array.map 등의 코드로 이루어져 있습니다.
이를 구형 브라우저에서 인식하기 위해서는 Babel을 사용하여 트랜스파일링이 필요합니다.
Babel 설정
Babel에서 core-js를 사용하려면 @babel/preset-env
프리셋을 적용해야 합니다.
이전에는@babel/polyfill
프리셋을 사용했지만 7.4.0 버전 이후로는 deprecated되었습니다.
@babel/polyfill
글로벌 파일에 @babel/polyfill
을 직접 import하여 사용했던 방식이었습니다.
core-js를 단순하게 import하기 때문에 전역 환경을 오염시키는 문제가 존재하였습니다. (babel-polyfill)
import '@babel/polyfill';
// Cover all standardized ES6 APIs.
import 'core-js/es6';
// Standard now
import 'core-js/fn/array/includes';
import 'core-js/fn/array/flat-map';
import 'core-js/fn/string/pad-start';
import 'core-js/fn/string/pad-end';
import 'core-js/fn/string/trim-start';
import 'core-js/fn/string/trim-end';
import 'core-js/fn/symbol/async-iterator';
import 'core-js/fn/object/get-own-property-descriptors';
import 'core-js/fn/object/values';
import 'core-js/fn/object/entries';
import 'core-js/fn/promise/finally';
// Ensure that we polyfill ES6 compat for anything web-related, if it exists.
import 'core-js/web';
import 'regenerator-runtime/runtime';
@babel/preset-env
core-js
와 targets
옵션을 사용하여 상세한 타깃 브라우저를 설정할 수 있으며, polyfill 변환 기능을 제공하는 플러그인 입니다.
-
corejs: 사용하려는 core-js 라이브러리 버전을 타깃합니다. (useBuitIns 옵션이 활성화 될 때 적용)
-
useBiuiltIns
-
entry: core-js 모듈을 직접 import하여 사용하는 방법입니다.
import "core-js/stable/array"; const fruits = ["사과", "바나나"]; fruits.forEach((item) => { console.log(item); }); const apple = fruits.filter((item) => item === "사과"); //transfile ("use strict"); require("core-js/modules/es.array.concat.js"); require("core-js/modules/es.array.copy-within.js"); require("core-js/modules/es.array.fill.js"); require("core-js/modules/es.array.filter.js"); require("core-js/modules/es.array.find.js"); require("core-js/modules/es.array.find-index.js"); ... require("core-js/modules/es.array.unscopables.flat-map.js"); require("core-js/modules/es.object.to-string.js"); require("core-js/modules/es.string.iterator.js"); var fruits = ["사과", "바나나"]; fruits.forEach(function (item) { console.log(item); }); var apple = fruits.filter(function (item) { return item === "사과"; });
-
usage: 코드에 필요한 polyfill 구문만 자동으로 삽입되도록 하는 방식입니다.
//example const fruits = ['사과', '바나나']; fruits.forEach((item) => { console.log(item); }); const apple = fruits.filter((item) => item === '사과'); //transfile ('use strict'); require('core-js/modules/es.object.to-string.js'); require('core-js/modules/es.array.filter.js'); var fruits = ['사과', '바나나']; fruits.forEach(function (item) { console.log(item); }); var apple = fruits.filter(function (item) { return item === '사과'; });
-
-
targets: 타깃 browserlist 옵션을 따로 지정할 수 있습니다.
//지원 타겟팅 환경: android, chrome, deno, edge, electron, firefox, ie, ios, node, opera, rhino, safari, samsung. { ... "targets": { //특정 브라우저 또는 런타임 환경 버전에 맞춰 타겟팅 "chrome": "58", "ie": "11" } } { ... "targets": { //특정 지역의 브라우저 사용율, 버전에 맞춰 타겟팅 "browsers": [" > 5% in KR", "last 2 Chrome version"] } }
@babel/preset-env로 구성한 최종 설정
//.babelrc.json
{
"presets": [
[
"@babel/preset-env",
{
"corejs": "3.29",
"useBuiltIns": "entry",
"targets": {
"ie": "11"
}
}
],
"@babel/preset-react" //react 환경을 위한 babel preset
],
"plugins": []
}
@babel/plugin-transform-runtime
@babel/runtime
과 core-js3
가 통합된 transform-runtime
플러그인을 사용하면 전역 스코프를 오염시키지 않습니다. 또한, 기존 @babel/runtime
에서 지원하지 않았던 인스턴스 메서드도 지원합니다.
var _interopRequireDefault = require('@babel/runtime-corejs3/helpers/interopRequireDefault');
var _forEach = _interopRequireDefault(require('@babel/runtime-corejs3/core-js-stable/instance/for-each'));
var _filter = _interopRequireDefault(require('@babel/runtime-corejs3/core-js-stable/instance/filter'));
var fruits = ['사과', '바나나'];
(0, _forEach.default)(fruits).call(fruits, function (item) {
console.log(item);
});
var apple = (0, _filter.default)(fruits).call(fruits, function (item) {
return item === '사과';
});
corejs
속성으로 런타임 헬퍼를 지정할 수 있습니다.
- fasle (기본): babel/runtime
- 2: @babel/runtime-corejs2
- 3: babel/runtime-corejs3
//.babelrc.json
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"ie": 11
},
"modules": "cjs"
}
],
"@babel/preset-react"
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 3
}
]
]
}
최종적으로 @babel/plugin-transform-runtime
설정을 통해 ie11 환경에서도 react 페이지를 정상적으로 실행시킬 수 있었습니다. fetch
문법은 ECMA Script 명시된 문법은 아니므로, 별도의 polyfill 추가가 필요합니다.
유용한 Polyfill
whatwg-fetch
fetch를 제공해주는 polyfill 입니다.
//📁src/index.js
import 'whatwg-fetch'
fetch(...);
react-app-polyfill
ie
브라우저에 초점을 맞춘 react에서 제공하는 polyfill 입니다.
아래 5가지 polyfill을 제공합니다.
- promise, async-await
- fetch
- Object.assign
- Symbol
- Array.from, Array Spread
//📁src/index.js
import 'react-app-polyfill/ie9';
import 'react-app-polyfill/stable';