Carga moderna de guiones

Pasar el código correcto para cada navegador no es tarea fácil.

En este artículo, consideraremos varias opciones sobre cómo se puede resolver este problema.



Pasar código moderno por un navegador moderno puede mejorar en gran medida el rendimiento. Sus paquetes de JavaScript podrán contener una sintaxis moderna más compacta u optimizada y admitirán navegadores más antiguos.

Entre las herramientas para desarrolladores, domina el patrón de módulo / módulo de carga declarativa de código moderno o heredado, que proporciona a los navegadores fuentes y le permite decidir cuáles usar:

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

Desafortunadamente, no todo es tan simple. El enfoque HTML que se muestra arriba desencadena una recarga de script en Edge y Safari .

Que se puede hacer


Dependiendo del navegador, necesitamos entregar una de las opciones para los scripts compilados, pero un par de navegadores antiguos no admiten toda la sintaxis necesaria para esto.

En primer lugar, está Safari Fix . Safari 10.1 admite módulos JS, no el atributo nomodule en los scripts, lo que le permite ejecutar código moderno y heredado. Sin embargo, el evento de beforeload no estándar admitido por Safari 10 y 11 se puede usar para nomodule .

Método uno: descarga dinámica


Puede solucionar estos problemas implementando un pequeño cargador de scripts. Similar a cómo funciona LoadCSS . En lugar de esperar la implementación de módulos ES y el atributo nomodule en los nomodule , puede intentar ejecutar un script de módulo como una "prueba con una prueba de fuego" y, en función del resultado, elegir descargar un código moderno o heredado.

 <!-- 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 ('noModule' in s) { // notice the casing s.type = 'module' s.src = '/modern.js' } else { s.src = '/legacy.js' } document.head.appendChild(s) }) </script> 

Pero con este enfoque, debe esperar a que se complete el script del módulo "tornasol" antes de implementar el script correcto. Esto sucede porque <sript type="module"> siempre funciona de forma asíncrona. ¡Pero hay una mejor manera!

Puede implementar la opción independiente comprobando si el nomodule es nomodule con el navegador. Esto significa que consideraremos navegadores como Safari 10.1 como obsoletos, incluso si admiten módulos. Pero podría ser lo mejor . Aquí está el código relevante:

 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) 

Esto se puede convertir rápidamente en una función que carga código moderno o heredado, y también proporciona carga asincrónica de ellos:

 <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> 

¿Cuál es el compromiso aquí?

Precarga

Dado que la solución es completamente dinámica, el navegador no podrá detectar nuestros recursos de JavaScript hasta que inicie el código de arranque que escribimos para insertar scripts modernos o heredados. Normalmente, un navegador escanea HTML en busca de recursos que puede descargar por adelantado. Este problema se resuelve, pero no de manera ideal: puede precargar la versión moderna del paquete en los navegadores modernos usando <link rl=modulpreload> .

Desafortunadamente, hasta ahora solo Chrome admite la modulepreload .

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

Si esta técnica es adecuada para usted, puede reducir el tamaño del documento HTML en el que incrusta estos scripts. Si su carga útil es pequeña, como una pantalla de bienvenida o un código de descarga de la aplicación cliente, es poco probable que dejar caer el escáner de precarga afecte el rendimiento. Y si dibuja una gran cantidad de HTML importante en el servidor para enviarlo a los navegadores, entonces el escáner de precarga le será útil y el enfoque descrito no será la mejor opción para usted.

Así es como podría verse esta solución en uso:

 <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> 

También debe tenerse en cuenta que la lista de navegadores que admiten módulos JS es casi la misma que la que admite <link rl=preload> . Para algunos sitios, puede ser apropiado usar <link rl=preload as=script crossorigin> lugar de modulepreload . El rendimiento puede deteriorarse porque la precarga de scripts clásicos no implica un análisis uniforme con el tiempo, como es el caso de la modulepreload .

Método dos: seguimiento del agente de usuario


No tengo un ejemplo de código adecuado, ya que el seguimiento de User Agent es una tarea no trivial. Pero luego puedes leer el excelente artículo en Smashing Magazine.

De hecho, todo comienza con el mismo <scrit src=bundle.js> en HTML para todos los navegadores. Cuando se solicita bundle.js, el servidor analiza la cadena del Agente de usuario del navegador solicitante y selecciona qué JavaScript devolver: moderno o heredado, dependiendo de cómo se reconoció el navegador.

El enfoque es universal, pero conlleva graves consecuencias:

  • Dado que se requieren servidores inteligentes, este enfoque no funcionará en condiciones de implementación estática (generadores de sitios estáticos, Netlify, etc.).
  • El almacenamiento en caché de estas URL de JavaScript ahora depende del Agente de usuario, que es muy volátil.
  • La definición de AU es difícil y puede conducir a una clasificación falsa.
  • La línea de User Agent es fácil de falsificar, y todos los días aparecen nuevos UA.

Una forma de evitar estas limitaciones es combinar el patrón de módulo / nomódulo con la diferenciación del Agente de usuario para evitar el envío de múltiples versiones del paquete a la misma dirección. Este enfoque reduce la capacidad de almacenamiento en caché de la página, pero proporciona una precarga eficiente: el servidor generador de HTML sabe cuándo usar la modulepreload y cuándo 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); } 

Para los sitios que ya generan HTML en el servidor en respuesta a cada solicitud, esta puede ser una transición efectiva para descargar scripts modernos.

Método tres: navegadores viejos y finos


El efecto negativo del patrón de módulo / módulo es visible en versiones anteriores de Chrome, Firefox y Safari: su número es muy pequeño porque los navegadores se actualizan automáticamente. Con Edge 16-18, la situación es diferente, pero hay esperanza: las nuevas versiones de Edge utilizarán el motor de renderizado basado en Chromium, que no tiene tales problemas.

Para algunas aplicaciones, esto sería un compromiso ideal: descargue la versión moderna del código en el 90% de los navegadores y dé el código heredado a los antiguos. La carga en navegadores antiguos aumentará.

Por cierto, ninguno de los Agentes de usuario para los que un reinicio de este tipo es un problema no ocupa una parte significativa del mercado móvil. Por lo tanto, es poco probable que la fuente de todos estos bytes adicionales sean dispositivos móviles o dispositivos con un procesador débil.

Si está creando un sitio al que acceden principalmente los navegadores móviles o nuevos, entonces para la mayoría de estos usuarios, el tipo más simple de patrón de módulo / módulo es adecuado. Solo asegúrate de agregar la solución Safari 10.1 si te llegan dispositivos iOS más antiguos.

    iOS-. <!-- 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> 

Método cuatro: aplicar los términos del paquete


Una buena solución sería usar nomodule para descargar condicionalmente paquetes con código que no es necesario en los navegadores modernos, como polyfills. Con este enfoque, en el peor de los casos, el polyfill se cargará o incluso se ejecutará (en Safari 10.1), pero el efecto de esto se limitará a "re-polyfilling". Teniendo en cuenta que hoy prevalece el enfoque de descarga y ejecución de polyfills en todos los navegadores, esta puede ser una mejora digna.

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

Puede configurar la CLI angular para usar este enfoque con polyfills, como lo demostró Minko Gachev. Al conocer este enfoque, me di cuenta de que puede habilitar la inyección automática de polyfill en preact-cli; este PR demuestra lo fácil que es implementar esta técnica.

Y si usa WebPack, entonces hay un complemento conveniente para html-webpack-plugin , que hace que sea fácil agregar un nomodule a paquetes con polyfills.

Entonces, ¿qué elegir?


La respuesta depende de tu situación. Si está creando una aplicación cliente y su HTML contiene un poco más que <sript> , es posible que necesite el primer método .

Si está creando un sitio que se representa en el servidor y puede permitirse el almacenamiento en caché, entonces el segundo método puede ser adecuado para usted .

Si utiliza la representación universal , la ganancia de rendimiento que ofrece el escaneo previo a la carga puede ser muy importante. Por lo tanto, preste atención al tercer o cuarto método. Elija lo que se adapte a su arquitectura.

Personalmente, elijo centrarme en la duración del análisis en dispositivos móviles y no en el costo de la descarga en versiones de escritorio. Los usuarios móviles perciben los costos de análisis y transferencia de datos como gastos reales (consumo de batería y tarifas de transferencia de datos), mientras que los usuarios de escritorio no tienen tales restricciones. También procedo de la optimización para el 90% de los usuarios: la audiencia principal de mis proyectos utiliza navegadores modernos y / o móviles.

Que leer


¿Quieres aprender más sobre este tema? Puedes comenzar desde aquí:

Source: https://habr.com/ru/post/473994/


All Articles