정보실

웹학교

정보실

javascript 최신 스크립트 로딩

본문

올바른 브라우저에 올바른 코드를 제공하는 것은 까다로울 수 있습니다. 몇 가지 옵션이 있습니다.


최신 코드를 최신 브라우저에 제공하면 성능이 향상 될 수 있습니다. JavaScript 번들에는 이전 브라우저를 계속 지원하면서 보다 간결하거나 최적화 된 최신 구문이 포함될 수 있습니다.


https://jasonformat.com/modern-script-loading/ 


툴링 생태계는 최신 VS 레거시 코드를 선언적으로 로드 하기 위해 모듈/모듈 없음 패턴을 사용하여 통합되어 브라우저에 두 가지 소스를 모두 제공하고 사용할 코드를 결정할 수 있습니다.

<script type="module" src="/modern.js"></script> <script nomodule src="/legacy.js"></script>  


불행히도, 그렇게 간단하지 않습니다. 위에 표시된 HTML 기반 접근 방식은 Edge 및 Safari에서 스크립트의 오버 페칭을 트리거 합니다.


무엇을 할 수 있습니까? 


우리는 무엇을 할 수 있습니까? 우리는 브라우저에 따라 두 개의 컴파일 대상을 제공하려고 하지만 몇 가지 구형 브라우저는 그렇게 하는 멋진 구문을 지원하지 않습니다.


먼저, 사파리 픽스가 있습니다. Safari 10.1은 스크립트에서 nomodule 속성이 아닌 JS 모듈을 지원하므로 최신 코드와 레거시 코드를 모두 실행할 수 있습니다. 고맙게도, Sam은 Safari 10 & 11에서 지원되는 비표준 사전로드 이벤트를 사용하여 모듈을 채우지 않는 방법을 찾았습니다.


옵션 1 : 동적으로 로드 


LoadCSS 작동 방식과 유사한 작은 스크립트 로더를 구현하여 이러한 문제를 피할 수 있습니다. ES 모듈과 nomodule 속성을 모두 구현하기 위해 브라우저에 의존하는 대신 모듈 스크립트를 "리트머스 테스트"로 실행 한 다음 해당 테스트 결과를 사용하여 최신 코드 또는 레거시 코드를 로드할지 여부를 선택할 수 있습니다.

<!-- use a module script to detect modern browsers: --> <script type=module> self.modern = true </script> <!-- now use that flag to load modern VS legacy code: --> <script> addEventListener('load', function() { var s = document.createElement('script') if (self.modern) { s.src = '/modern.js' s.type = 'module' } else { s.src = '/legacy.js' } document.head.appendChild(s) }) </script>  


그러나 이 솔루션은 첫 번째 "litmus test"모듈 스크립트가 실행될 때까지 기다려야 올바른 스크립트를 주입 할 수 있습니다. <script type = module>은 항상 비동기이기 때문입니다. 더 좋은 방법이 있습니다!


브라우저가 모듈을 지원하지 않는지 확인하여 독립형 변형을 구현할 수 있습니다. 이것은 Safari 10.1과 같은 브라우저가 모듈을 지원하더라도 레거시로 취급된다는 것을 의미하지만 좋은 일입니다. 그 코드는 다음과 같습니다.


var s = document.createElement('script') if ('noModule' in s) { // notice the casing s.type = 'module' s.src = '/modern.js' } else s.src = '/legacy.js' } document.head.appendChild(s)  


이것은 최신 코드 나 레거시 코드를 로드하는 함수로 빠르게 롤업 할 수 있으며 두 코드가 모두 비동기 적으로로드되도록합니다.


<script> $loadjs("/modern.js","/legacy.js") function $loadjs(src,fallback,s) { s = document.createElement('script') if ('noModule' in s) s.type = 'module', s.src = src else s.async = true, s.src = fallback document.head.appendChild(s) } </script>  


이 솔루션의 문제점은 동적이기 때문에 브라우저가 최신 스크립트와 레거시 스크립트를 주입하기 위해 작성한 부트 스트랩 코드를 실행할 때까지 JavaScript 리소스를 검색 할 수 없다는 것입니다. 일반적으로 브라우저는 미리 로드 할 수 있는 리소스를 찾기 위해 스트리밍 되는 HTML을 스캔합니다. 완벽하지는 않지만 해결책이 있습니다. <link rel = modulepreload>를 사용하여 최신 브라우저에서 최신 버전의 번들을 사전 로드 할 수 있습니다. 불행히도 지금까지 Chrome 만 모듈 프리 로드를 지원합니다.


<link rel="modulepreload" href="/modern.js"> <script type=module>self.modern=1</script> <!-- etc -->  


이 기술이 효과가 있는지 여부는 해당 스크립트를 포함하는 HTML 문서의 크기로 내려갈 수 있습니다. HTML 페이 로드가 스플래시 화면만큼 작거나 클라이언트 측 응용 프로그램을 부트스트랩 하기에 충분할 경우 사전 로드 스캐너를 포기해도 성능에 영향을 줄 가능성이 적습니다. 브라우저에서 스트리밍 할 수 있도록 의미 있는 HTML을 서버로 렌더링 하는 경우 사전 로드 스캐너가 가장 적합하며 이것이 최선의 방법이 아닐 수도 있습니다.


이 솔루션이 prod에서 다음과 같이 나타날 수 있습니다.


<link rel="modulepreload" href="/modern.js"> <script type=module>self.modern=1</script> <script> $loadjs("/modern.js","/legacy.js") function $loadjs(e,d,c){c=document.createElement("script"),self.modern?(c.src=e,c.type="module"):c.src=d,document.head.appendChild(c)} </script>  


JS 모듈을 지원하는 브라우저 세트는 <link rel = preload>를 지원하는 브라우저 세트와 매우 유사합니다. 일부 웹 사이트의 경우 modulepreload에 의존하지 않고 <link rel = preload as = script crossorigin>을 사용하는 것이 좋습니다. 클래식 스크립트 프리 로딩은 모듈 프리 로드 뿐만 아니라 시간이 지남에 따라 구문 분석 작업을 분산 시키지 않기 때문에 성능이 저하 될 수 있습니다.


옵션 2 : 사용자 에이전트 스니핑 


User Agent 감지가 사소한 것이 아니기 때문에 이에 대한 간결한 코드 샘플이 없지만 훌륭한 Smashing Magazine 기사가 있습니다.


기본적으로 이 기술은 모든 브라우저의 HTML에서 동일한 <script src = bundle.js>로 시작합니다. bundle.js가 요청되면 서버는 요청 브라우저의 사용자 에이전트 문자열을 구문 분석하고 해당 브라우저가 최신으로 인식되는지 여부에 따라 최신 JavaScript 또는 레거시 JavaScript를 반환할지 여부를 선택합니다.


이 방법은 다양하지만 몇 가지 심각한 의미가 있습니다.

  • 서버 스마트는 필수이므로 정적 배포 (정적 사이트 생성기, Netlify 등)에는 작동하지 않습니다.
  • 이러한 JavaScript URL에 대한 캐싱은 사용자 에이전트에 따라 달라집니다.
  • UA 탐지가 어렵고 허위 분류하기 쉽습니다.
  • User Agent 문자열은 쉽게 스푸핑 가능하며 매일 새로운 UA가 도착합니다.

이러한 제한을 해결하는 한 가지 방법은 처음에 여러 번들 버전을 전송하지 않도록 모듈 / 모듈 없음 패턴을 사용자 에이전트 차별화와 결합하는 것입니다. 이 접근 방식은 여전히 ​​페이지의 캐시 가능성을 줄이지 만 HTML을 생성하는 서버가 modulepreload 또는 preload를 사용할지 여부를 알고 있기 때문에 효과적인 사전 로드를 허용합니다.

function renderPage(request, response) { let html = `<html><head>...`; const agent = request.headers.userAgent; const isModern = userAgent.isModern(agent); if (isModern) { html += ` <link rel=modulepreload href=modern.mjs> <script type=module src=modern.mjs></script> `; } else { html += ` <link rel=preload as=script href=legacy.js> <script src=legacy.js></script> `; } response.end(html); } 


각 요청에 대한 응답으로 서버에서 이미 HTML을 생성하는 웹 사이트의 경우 현대적인 스크립트 로드에 효과적인 솔루션이 될 수 있습니다.


옵션 3 : 이전 브라우저에 불이익 


모듈 / 모듈 없음 패턴의 악영향은 사용자가 최신 버전으로 자동 업데이트 되기 때문에 사용이 매우 제한된 이전 버전의 Chrome, Firefox 및 Safari 브라우저 버전에서 볼 수 있습니다. Edge 16-18에는 적용되지 않지만 새로운 버전의 Edge에는 이 문제가 발생하지 않는 Chromium 기반 렌더러가 사용됩니다.


일부 응용 프로그램에서는 이를 절충으로 받아들이는 것이 합리적 일 수 있습니다. 구식 브라우저의 추가 대역폭을 희생하면서 최신 코드를 브라우저의 90 %에 제공 해야 합니다. 특히,이 오버 페칭 문제로 어려움을 겪고 있는 사용자 에이전트는 모바일 시장 점유율이 높지 않기 때문에 값 비싼 모바일 계획이나 느린 프로세서가 장착 된 장치를 통해 이러한 바이트를 얻을 가능성이 적습니다.


사용자가 주로 모바일 또는 최신 브라우저를 사용하는 사이트를 구축하는 경우 가장 간단한 형태의 모듈 / 모듈 없음 패턴이 대다수의 사용자에게 적합합니다. 약간 오래된 iOS 기기에서 사용하는 경우 Safari 10.1 수정 프로그램을 포함시켜야 합니다.


<!-- polyfill `nomodule` in Safari 10.1: --> <script type=module> !function(e,t,n){!("noModule"in(t=e.createElement("script")))&&"onbeforeload"in t&&(n=!1,e.addEventListener("beforeload",function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()},!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove())}(document) </script> <!-- 90+% of browsers: --> <script src=modern.js type=module></script> <!-- IE, Edge <16, Safari <10.1, old desktop: --> <script src=legacy.js nomodule async defer></script>  



옵션 4 : 조건부 번들 사용 


여기에서 한 가지 영리한 접근 방법은 nomodule을 사용하여 폴리 필과 같은 최신 브라우저에서 필요하지 않은 코드가 포함 된 번들을 조건부로 로드하는 것입니다. 이 방법을 사용하는 경우 최악의 경우는 폴리 필이 로드 되거나 실행될 수도 있지만 (Safari 10.1에서), 그 효과는 "과다 폴리 필"로 제한됩니다. 현재 널리 사용되는 접근 방식은 모든 브라우저에서 폴리 필을 로드하고 실행하는 것이므로 전체적으로 개선 될 수 있습니다.


<!-- newer browsers won't load this bundle: --> <script nomodule src="polyfills.js"></script> <!-- all browsers load this one: --> <script src="/bundle.js"></script>  


Minko Gechev가 시연 한 것처럼 폴리 필에이 접근 방식을 사용하도록 각도 CLI를 구성 할 수 있습니다. 이 접근법에 대해 읽은 후, preact-cli에서 자동 폴리 필 주입을 전환하여 사용할 수 있음을 깨달았습니다.이 PR은이 기술을 채택하는 것이 얼마나 쉬운지를 보여줍니다.


Webpack을 사용하는 사람들에게는 html-webpack-plugin을위한 편리한 플러그인이 있어 polyfill 번들에 모듈을 쉽게 추가 할 수 없습니다.



어떻게 해야 합니까? 


대답은 사용 사례에 따라 다릅니다. 클라이언트 측 응용 프로그램을 구축 중이고 앱의 HTML 페이 로드가 <script> 이상인 경우 옵션 1이 강력 할 수 있습니다. 서버 렌더링 웹 사이트를 구축 중이며 캐싱에 영향을 줄 수 있는 경우 옵션 2가 적합할 수 있습니다. 범용 렌더링을 사용하는 경우 사전 로드 스캔이 제공하는 성능 이점이 매우 중요 할 수 있으며 옵션 3 또는 옵션4를 살펴보십시오. 아키텍처에 적합한 것을 선택하십시오.


개인적으로, 나는 일부 데스크탑 브라우저의 다운로드 비용보다는 모바일에서 더 빠른 구문 분석 시간을 위해 최적화 하기로 결정하는 경향이 있습니다. 모바일 사용자는 배터리 소모 및 데이터 요금과 같은 실제 비용으로 구문 분석 및 데이터 비용을 겪는 반면 데스크톱 사용자는 이러한 제약이 없는 경향이 있습니다. 또한 90 %를 최적화하고 있습니다. 내가 작업하는 작업의 경우 대부분의 사용자가 최신 브라우저 및 / 또는 모바일 브라우저를 사용하고 있습니다.


추가 자료 


이 공간에 대해 자세히 알아보고 싶으십니까? 파기를 시작할 곳은 다음과 같습니다.


Phil의 webpack-esnext-boilerplate에는 훌륭한 추가 컨텍스트가 있습니다.

Ralph는 Next.js에서 module / nomodule을 구현했으며 이러한 문제를 해결하기 위해 노력하고 있습니다.




  • 트위터로 보내기
  • 페이스북으로 보내기
  • 구글플러스로 보내기
  • 카카오톡으로 보내기

페이지 정보

조회 31회 ]  작성일19-08-16 12:17

웹학교