본문 바로가기
카테고리 없음

오래된 브라우저에서 자바스크립트가 지원을 안해요, 폴리필(Polyfill)

by klm hyeon woo 2024. 10. 11.

예전 에디터 개발을 진행하면서 오래된 구형 브라우저를 지원해야했던 적이 있었습니다. 최신 자바스크립트 문법은 모던 브라우저에서는 정상적으로 동작을 하지만, 동일한 코드로 동일한 동작을 구현함에 있어서 오래된 버전의 브라우저에서는 현재 자바스크립트가 사용하고 있는 객체가 undefined으로 나오기도 합니다. 예를 들어 아래와 같은 코드는 최신 브라우저에서 잘 동작하지만 오래된 브라우저에서는 실패합니다. 그 이유는 객체나 메서드에 대한 구현이 없기 때문입니다.

[1, 2, 3].at(-1);

Promise.resolve(1);

new Set (1, 2, 3);

 

이러한 문제를 해결하기 위해서는 오래된 브라우저에서 존재하지 않는 기능에 대해 개발자가 관여를 해야하는데, 이때 이를 해결하기 위해 사용할 수 있는 스크립트를 Polyfill이라고 합니다. 대부분의 Polyfill은 아래와 같이 이미 브라우저에 포함되어있는지 체크하고, 없으면 값을 채워주는 형태로 동작을 합니다. 오늘 이 시간에는 가볍게만 알고 있었던 Polyfill에 대한 개념을 조금 더 심화해서 배워보려고 합니다.

Array.prototype.at = Array.prototype.at ?? /* Array.prototype.at에 대한 자체 구현 로직 */

 

위 스크립트를 실행한 이후에는 오래된 브라우저에서도 안전하게 [1, 2, 3].at(-1) 코드를 실행할 수 있습니다.

표준적으로 사용되는 Polyfill들은 core-js 레포지토리에 모여있습니다. 아래의 코드를 통해 대부분의 ECMAScript 표준 객체와 메서드를 오래된 브라우저에서도 사용할 수 있습니다. core-js는 npm 개발 환경에서 많이 사용하는 Polyfill 라이브러리입니다.

import 'core-js/actual';

 

그렇다면 우리가 흔히 알고 있는 ES2015 이상의 문법들을 브라우저에서 호환될 수 있도록 변환해주는 babel과의 차이점은 무엇일까요?

Polyfill과 유사하지만 차이점은 분명합니다, Polyfill은 최신 문법에 대해 지원하지 않는 하위 브라우저를 위한 추가적인 기능 제공을 수행하며 Babel은 ES2015 이상의 문법들을 하위 버전으로 변환해주는 기능을 수행하는데 기능에 따라 변환하지 못하는 것들도 존재할 수 있습니다. 

 

· 새로운 전역 Window에 추가된 객체 (Promise, Map, Set)

· 새로운 메서드 (Array.from, Array.include)

 

이렇게 Babel이 제공해주지 못하는 문법을 사용하기 위해서는 별도의 Polyfill을 추가해주어야 합니다.

Polyfill을 통해 폭넓은 브라우저를 지원할 수 있다는 장점이 있지만, 불러와야하는 JavaScript 코드의 양이 많아진다는 단점을 가지고 있습니다. 실행해야하는 Polyfill 스크립트가 많아질수록 사용자가 경험하는 웹 서비스의 성능은 나빠집니다.

특히 위의 코드와 같이 설정을 하게 되면 최신 버전의 브라우저에서는 대부분의 ECMAScript 표준 객체와 메서드가 포함되어있음에도 불구하고 불필요한 Polyfill 스크립트를 내려받게됩니다. 이럴 경우에는 선택적으로 불러올 수 있는 방법을 찾아야합니다.

1. Babel과 Polyfill을 이용한 구형 브라우저 대응하기

첫 번째 방법으로는 @babel/preset-env Smart Preset을 사용하는 것입니다.

이 Smart Preset은 이미 정의된 브라우저 목록에 따라서 자동으로 필요 없는 Polyfill을 제거해줍니다.

예를 들어 IE11을 지원을 해야한다면, 아래처럼 babel.config.js를 설정할 수 있습니다.

module.exports = {
	presets: [
    	['@babel/preset-env', { targets: { ie: 11 } }],
    ]
    /* 그 외의 설정 값들 */
}

 

이후 동일하게 core-js/actual을 import 하더라도 IE11에 필요한 Polyfill 목록만 포함되는 것을 확인할 수 있습니다.

require("core-js/modules/es.symbol.js");
require("core-js/modules/es.symbol.description.js");
require("core-js/modules/es.symbol.async-iterator.js");
require("core-js/modules/es.symbol.has-instance.js");
require("core-js/modules/es.symbol.is-concat-spreadable.js");
require("core-js/modules/es.symbol.iterator.js");
... (약 221개)

 

IE11을 지원 브라우저 목록에서 제외를 하게 되면 훨씬 적은 25개의 Polyfill이 포함됩니다.

module.exports = {
	presets: [
    	['@babel/preset-env', { targets: 'defaults, not ie 11' }],
    ],
    /* 그 외의 설정 값들 */
}
require("core-js/modules/es.error.cause.js");
require("core-js/modules/es.aggregate-error.cause.js");
require("core-js/modules/es.array.at.js");
require("core-js/modules/es.array.includes.js");
require("core-js/modules/es.object.has-own.js");
require("core-js/modules/es.regexp.flags.js");
require("core-js/modules/es.string.at-alternative.js");
require("core-js/modules/es.typed-array.at.js");
require("core-js/modules/esnext.array.find-last.js");
... (약 25개)

 

위와 같이 @babel/preset-env에 브라우저 지원 범위를 설정하여 Polyfill을 안정적으로 포함하면서 스크립트의 크기를 감축시킬 수 있는 방법으로 설계를 진행할 수 있습니다.

2. User-Agent 헤더에 따라 동적 스크립트를 이용한 구형 브라우저 대응하기

User-Agent 헤더를 통해 브라우저를 파악하고, 스크립트 실행을 위한 적절한 코드를 제공할 수도 있습니다.

Babel을 올바르게 설정함으로서 포함되는 Polyfill 스크립트의 크기를 줄일 수는 있지만, 최신 버전의 브라우저에서 불필요한 스크립트를 내려받게 되는 문제는 어느정도 동일합니다. Chrome 최신 버전은 문제 없이 [1, 2, 3].at(-1)을 실행할 수 있지만, 관련한 Polyfill을 내려받습니다. 

 

polyfill.io 서비스에서는 https://polyfill.io/v3/polyfill.min.js 라고 하는 경로로 동적인 Polyfill 스크립트를 제공합니다.

최신 버전의 Chrome에서 해당 경로를 접속하면, 아무 Polyfill 스크립트가 내려오지 않도록 할 수 있습니다.

$ curl -XGET "https://polyfill.io/v3/polyfill.min.js" \
   -H "User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Mobile Safari/537.36" \
   -v
/* 빈 스크립트 */

반대로, IE11에서 실행을 하게 되면 많은 양의 Polyfill 스크립트가 내려온다는 것을 확인할 수 있습니다.

$ curl -XGET "https://polyfill.io/v3/polyfill.min.js" \
   -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko" \
   -v
(function(self, undefined) {!function(t){t.DocumentFragment=function n(){return document.createDocumentFragment() # ...

이렇게 User-agent에 따라 동적으로 Polyfill 스크립트를 생성하면 최신 브라우저에서는 아무 Polyfill도 내려주지 않고, 오래된 브라우저에서는 필요한 Polyfill만 내려줄 수 있게 됩니다. polyfill.io에서 제공된 스크립트는 해당 브라우저에서 지원되지 않는 문법을 자체적으로 사용 가능한 문법으로 감싸주어 사용할 수 있도록 지원을 해줍니다. 

 

polyfill.io는 URL 파라미터를 통해 필요한 상세 옵션을 전달할 수 있습니다.

 

· feature

필요한 기능만 선택적으로 요청할 수 있습니다.

https://polyfill.io/v3/polyfill.min.js?features=Array.from&CArray.isArray&fetch

 

· flags

필요한 폴리필을 모든 브라우저에서 요청하려면 flags에 always 매개변수를 전달하면 됩니다. gated 매개변수를 전달하면 제공받은 브라우저에서 동작하는지 확인하고 필요한 경우에만 폴리필을 제공합니다. 아무것도 명시하지 않을 경우에는 기본적으로 브라우저가 해당 기능을 지원하면 Polyfill 스크립트를 로드하지 않는 방식으로 동작하게 됩니다.

 

· unknown

Polyfill 스크립트를 지원하지 않는 브라우저에서는 기능을 내려줄지 결정해야합니다. ignore 설정을 하면 지원하지 않는 브라우저에서 폴리필을 제공하지 않고, polyfill을 전달하면 폴리필 코드를 제공합니다.

마치며

사실 에디터를 개발하면서 구형 브라우저 대응에 대한 부분을 고민을 많이 했었습니다. 프로젝트 자체에서 IE 대응을 위해 제이쿼리를 사용하여 진행을 했었는데, 새롭게 Polyfill 이라는 스크립트를 알게 되었고 어떻게 사용할까에 대한 궁금점에 참 컸던 것 같습니다. 이번 기회에 Polyfill에 대한 개념을 숙지할 수 있었고, 글을 마무리하면서 글의 내용을 요약하면 아래와 같습니다.

 

· Polyfill이란 신규 JavaScript API를 오래된 버전의 브라우저에서도 사용할 수 있도록 하는 방법, 그렇지만 Polyfill 스크립트가 방대해질 수록 웹 성능이 나빠지는 단점을 가지고 있습니다.

· babel의 @babel/preset-env 스마트 프리셋을 이용해 포함할 Polyfill 스크립트의 범위를 지정할 수 있으며, 다만 이 경우에도 최신 브라우저는 오래된 브라우저를 위한 Polyfill을 다운로드 받는다.

· User-agent 헤더에 따라 동적으로 Polyfill 스크립트를 생성할 수 있으며, 이로서 최신 브라우저에서 내려받는 Polyfill 스크립트를 거의 없게 만들 수 있습니다.

레퍼런스

 

Polyfill이 무엇인가요??

브라우저 호환성을 위한 polyfill 종류와 사용 방법을 소개합니다.

jinyisland.kr

 

똑똑하게 브라우저 Polyfill 관리하기

현대적인 JavaScript를 쓰면서도 넓은 범위의 기기를 지원하기 위한 Polyfill을 어떻게 똑똑하게 설정할 수 있는지 소개합니다.

toss.tech

 

댓글