Chrome Audit 500: Parte 1. Aterrizaje

Las Herramientas para desarrolladores de Chrome tienen la pestaña Auditoría. En él se encuentra una herramienta llamada Lighthouse, que sirve para analizar qué tan bien está hecha la aplicación web.

imagen

Recientemente decidí probar una aplicación y los resultados me horrorizaron. Inmediatamente en varias secciones, la evaluación estaba en la zona roja. Comencé a estudiar qué estaba mal con mi aplicación. Y encontré en los resultados del análisis una gran lista de recomendaciones muy útiles, las cumplí y obtuve 500 puntos. Como resultado, la aplicación comenzó a ejecutarse mucho más rápido, y revisé varios conceptos con respecto al método de creación de aplicaciones. Y en este artículo quiero compartir las soluciones más interesantes a las que he llegado.

Si no tiene la capacidad de instalar Chrome, puede instalar Lighthouse desde npm y trabajar con él desde la consola.

En el artículo, no comparé cada recomendación con una sección específica; en cambio, dividí las secciones en soluciones que apliqué y que le gustaron a Ligthouse. Esto no es todo lo que recomienda, es solo lo más interesante. Las recomendaciones restantes son muy simples y, como el SEO, han sido familiares para todos.

Rendimiento


Selección de servidor


Este es el consejo más común, pero es este el fundamento de toda la productividad. Afortunadamente, encontrar una buena solución es simple, es cualquier centro de datos de Nivel 3 o Nivel 4. Este estado en sí no dice nada sobre la velocidad, dice que los propietarios se ocuparon de la calidad.

Inicialización de la aplicación


Una vez solo había html en los navegadores. Luego vino JavaScript y la lógica de negocios. Hoy hay tanta lógica en el cliente que html no puede soportarlo y ya no es necesario. Pero porque el navegador no puede comenzar a cargar desde el archivo JavaScript, tendremos que colocar un pequeño trozo de html para iniciar nuestra aplicación.

Idealmente, debería verse así:

<!DOCTYPE html> <html lang="ru"> <head> <title> </title> <link rel="manifest" href="./manifest.webmanifest"> <link rel="shortcut icon" href="content/images/favicon.ico" type="image/x-icon"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" charset="utf-8"/> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="viewport" content="width=device-width" /> <meta name="theme-color" content="#425566"> <meta name="Description" content=" "> </head> <body> <div id="loader"> loading </div> <script async> // todo:     </script> </body> </html> 

No debe haber ningún contenido, solo el código necesario para inicializar la aplicación, que cargará la aplicación y el contenido.

Este artículo no considera la optimización para bots, pero diré que es más fácil atrapar un bot específico y dar lo que un bot en particular necesita. El robot de Google comprenderá todo, desde el contenido que se cargará más tarde.

Usar pantalla de bienvenida


Todos estamos acostumbrados a las pantallas de bienvenida al cargar aplicaciones móviles, e incluso al cargar el sistema operativo, pero pocas personas usan pantallas de bienvenida en una aplicación web. Esto es lo que colocaremos en el bloque del cargador para que el usuario no se aburra mientras se carga la aplicación.

Como pantalla de bienvenida, como opción, puede usar animación CSS o simplemente una imagen, como se hace en los teléfonos móviles. La única condición es que debe ser muy ligero.

imagen

¿Qué obtenemos? Los usuarios con Internet lento recibirán instantáneamente una reacción del sitio, no admirarán la pantalla en blanco y se preguntarán si el sitio está funcionando o no. Es muy probable que los usuarios con Internet rápido ni siquiera lo vean, pero incluso tienen retrasos en Internet.

Como un ejemplo interesante de uso de pantallas de bienvenida, te daré el sitio de la estación de acoplamiento , donde una buena ola adorna una carga muy larga del sitio. De hecho, así es como las personas con Internet lento verán su aplicación.

E inmediatamente me apresuro a molestar a aquellos que piensan que salpicar una pantalla puede engañar a Lighthouse y colocar una gran aplicación detrás de él. Él lo ve todo y no le dará una buena nota para una aplicación pesada.

Inicialización de la aplicación


Ahora que estamos distrayendo la atención del usuario con imágenes, es hora de descargar la aplicación. Para hacer esto, insertamos el siguiente script dentro del bloque de script.

 // 1.  ServiceWorker,     PWA if (navigator.serviceWorker && !navigator.serviceWorker.controller) { navigator.serviceWorker.register('pwabuider-sw.js', { scope: './' }); } // 2.    [ "./content/font.css", "./content/grid.css" ].forEach(function(url){ var style = document.createElement("link"); style.href = url; style.rel = "stylesheet"; document.head.appendChild(style); }); // 3.    [ "./scripts/polyfills.min.js", //  vendors.min.js "./scripts/main.min.js" // spa  ].forEach(function(url){ const script = document.createElement("script"); script.src = url; script.async = false; document.head.appendChild(script); }); 

En que consiste:

  1. Conexión PWA : consideraremos en la sección correspondiente a continuación. Debe conectarlo lo antes posible, porque es posible que pwa ya tenga todo lo necesario para que el sitio funcione y no haya más solicitudes al servidor.
  2. Conecte estilos : conecte estilos según sea necesario. Idealmente, este código no debería existir en absoluto y los estilos deberían conectar sus componentes según sea necesario.
  3. Conectar guiones : conecta el programa. Debe consistir en solo dos de estos scripts. Todos los otros scripts (mapas, análisis, bibliotecas) que no afectan la visualización de la primera pantalla (no toda la página) se cargan después de dibujar la primera pantalla de la aplicación. El componente de análisis ya debería cargar el análisis después de cargar el programa. La calidad de los análisis no se verá afectada por esto, y los sistemas de análisis admiten la carga después de descargar el programa. Las tarjetas solo deben sumergirse después de que el usuario las escanee y lleguen a la pantalla. Con bibliotecas de terceros requeridas para que componentes específicos funcionen, de manera similar.

Como resultado, al cambiar un poco las prioridades, obtenemos una representación rápida de la aplicación. Por lo tanto, los usuarios y los robots de búsqueda están satisfechos con la velocidad y, al mismo tiempo, no infringen los análisis.

Carga diferida y renderizado


Un parámetro muy importante es la rapidez con que se dibuja la primera pantalla y el usuario puede comenzar a interactuar con esta página. Y aquí vale la pena usar las siguientes optimizaciones:

1. Representación perezosa. Es necesario dibujar solo esa parte de la página donde el usuario está mirando, y la representación de componentes pesados ​​o imágenes ya debe hacerse cuando el usuario ha saltado a ellos.

Una buena solución aquí son los componentes lazy-block y lazy-img:

 <div> <p></p> <lazy-img src="..."/> </div> <lazy-block>   </lazy-block> <lazy-block>   </lazy-block> <lazy-block>   </lazy-block> 

El punto es que monitorearán el desplazamiento del usuario y si el componente cae en el área de la pantalla, se dibujará. Esto se puede comparar con la técnica de desplazamiento virtual ( ejemplo ) que es familiar para todos en las paredes de las redes sociales. Podemos desplazarnos para siempre, pero nunca disminuyen la velocidad.

Pero no se olvide del robot de Google, que ve spa, pero no desplaza toda la página. Por lo tanto, si no te cuidas, él no verá tu contenido.

2. Si alguno de los componentes utiliza una dependencia externa, tendrá que cargarlo él mismo según sea necesario. Por ejemplo, puede ser un bloque con mapas, tablas o gráficos en 3D. Y recientemente, una forma de hacer esto en JS ha sido muy simple:

 class Demo { constructor() { this.init(); } private async init() { const module = await import('./external.mjs'); //   module.default(); module.doStuff(); } } 

Como resultado, el usuario solo carga lo que necesita, lo que ahorra en gran medida los recursos del usuario y del servidor.

Minimización de paquetes


Y ... sí, no lo pensaste, no se trata de minificación en Terser (UglifyJS), sino de dar solo lo que necesita a un navegador específico.

El hecho es que los navegadores están en constante evolución, tienen una nueva API, los desarrolladores están comenzando a usarla y, por compatibilidad con navegadores más antiguos, conectan polyfills y transpilers. Como resultado, surge el problema de que los usuarios con los navegadores más recientes, que representan alrededor del 80%, obtienen código diseñado para usuarios de IE11, transpilado y con archivos polifónicos.

El problema con este código es que contiene mucho texto adicional, y su rendimiento es 3 veces menor (según mis estimaciones subjetivas) que el original. Es mucho más lógico hacer varios paquetes para diferentes versiones de navegadores. Un paquete con el código ES2017 para Chrome 73 con un mínimo de polyfiles, un paquete con ES5 para IE11 con un mínimo de polyfiles, etc.

Escribí sobre cómo recopilar paquetes de diferentes versiones al mismo tiempo en un artículo anterior . Y para seleccionar la versión correcta en el navegador, modificamos ligeramente el script de conexión del programa:

 var esVersion = ".es2017"; try{ eval('"use strict"; class foo {}'); }catch(e){ esVersion = ".es5"; } [ "./scripts/polyfills" + esVersion + ".min.js", "./scripts/main" + esVersion + ".min.js" ].forEach(function(url){ const script = document.createElement("script"); script.src = url; script.async = false; document.head.appendChild(script); }); 

Como resultado, los usuarios de navegadores modernos recibirán el programa más liviano y productivo, y los usuarios de IE11 obtendrán lo que se merecen.

Otra forma interesante de minificar
Una biblioteca muy interesante para reducir paquetes en un 50% , desafortunadamente con resultados impredecibles.

Minimización de código


Un problema muy popular es cuando los desarrolladores comienzan a conectar todo lo que les interesa. Como resultado, a veces puede ver programas que pesan entre 5 y 15 mb o más. Por lo tanto, la elección de las bibliotecas debe abordarse con prudencia.

En lugar de marcos pesados ​​como Angular o React, es mejor elegir sus contrapartes más livianas: vue, preact, mithril, etc. De ninguna manera son inferiores a sus contrapartes eminentes, pero los ahorros en el tamaño del paquete pueden ser varias veces.

Evite usar bibliotecas pesadas. En lugar de usar bibliotecas como jquery, lodash, moment, rxjs y cualquier otra en un tamaño reducido> 100 kb, intente estudiar los algoritmos más profundamente y encontrar una solución en JS nativo. Como regla general, puede escribir de manera más simple en un script nativo y deshacerse de una gran dependencia innecesaria.

Minificación de imagen


Probablemente todos los desarrolladores front-end conozcan el formato de imagen webp y también sepan la necesidad de minimizar las imágenes al tamaño de visualización requerido. Pero por alguna razón, casi todos los desarrolladores ignoran esto. Y la razón de esto, en mi opinión, es extremadamente simple, la gente no entiende cómo se hace y se aplica en diferentes navegadores.

Por lo tanto, aquí daré una receta muy simple para resolver todos los problemas con las imágenes. Esta receta se basa en la herramienta de conversión y procesamiento de imágenes Sharp . Se destaca con una tubería muy reflexiva, debido a que la velocidad de procesamiento de imágenes es 30-40 veces mayor que la de los análogos. Y el tiempo de ensamblaje de cientos de imágenes de grandes fuentes en diferentes tamaños y formatos es comparable a la velocidad de ensamblaje de una interfaz moderna.

Para usar Sharp, necesita escribir un script, lo uso junto con glob para buscar imágenes de forma recursiva en el directorio con las imágenes de origen, y oculto el script en sí de la utilidad para ejecutar tareas de trago. Un ejemplo de mi asamblea:

 gulp.task('core-min-images', async () => { const fs = require('fs'); const path = require('path'); const glob = require('glob'); const sharp = require('sharp'); // 1.          glob const files = await new Promise((resolve, reject) => { glob('src/content/**/*.{jpeg,jpg,png}', {}, async (er, files) => { !er ? resolve(files) : reject(er); }); }); // 2.      let completed = 1; await Promise.all(files.map(async (file) => { const outFile = file.replace(/^src/, 'www'); const outDir = path.dirname(outFile); // 2.1.       if (!fs.existsSync(outDir)) { fs.mkdirSync(outDir, { recursive: true }); } // 2.2.    const origin = sharp(file); // 2.3.     1920     //       jpg/png  webp    (80%) const size1920 = origin.resize({ width: 1920 }); await size1920.toFile(outFile.replace(/\.(jpeg|jpg|png)$/, '-1920w.$1')); await size1920.toFile(outFile.replace(/\.(jpeg|jpg|png)$/, '-1920w.webp')); // 2.4.    480   const size480 = origin.resize({ width: 480 }); await size480.toFile(outFile.replace(/\.(jpeg|jpg|png)$/, '-480w.$1')); await size480.toFile(outFile.replace(/\.(jpeg|jpg|png)$/, '-480w.webp')); // 2.5.    120   const size120 = origin.resize({ width: 120 }); await size120.toFile(outFile.replace(/\.(jpeg|jpg|png)$/, '-120w.$1')); await size120.toFile(outFile.replace(/\.(jpeg|jpg|png)$/, '-120w.webp')); // 2.6.      console.log(`Complete image ${completed++} of ${files.length}:`, file); })); }); 

Como resultado, obtenemos imágenes optimizadas para diferentes tamaños de pantalla y diferentes navegadores de cada imagen fuente de gran tamaño. Ahora tenemos que aprender a usarlos. Aquí, también, todo es simple, si antes escribimos así:

 <img src="sample.jpg"/> 

Ahora necesitamos escribir así:

 <picture> <source srcset="img/sample-480w.webp" type="image/webp"> <source srcset="img/sample-480w.jpg" type="image/jpeg"> <img src="img/sample-480w.jpg" alt=" !"> </picture> 

Y luego el navegador mismo elegirá el formato más conveniente para él. También puede agregar esta opción con imágenes receptivas:

 <picture> <source srcset="img/sample-480w.webp, img/sample-960w.webp 2x" type="image/webp"> <source srcset="img/sample-480w.jpg, img/sample-960w.webp 2x" type="image/jpeg"> <img src="img/sample-480w.jpg" alt=" !"> </picture> 

Y teniendo en cuenta el hecho de que ahora es posible generar imágenes en la etapa de ensamblaje de la aplicación, resulta que todas las imágenes tendrán el mismo conjunto de formatos y permisos, lo que significa que podemos unificar esta lógica y ocultarla detrás de algún componente, por ejemplo, el mismo <lazy-img src="img/sample.jpg"> .

Minificación de estilo


Solo descargue estilos que usen sus componentes. Idealmente, cuando los estilos están vinculados a componentes, y se incrustan en la casa solo cuando se dibuja el componente en sí.

Minimiza los nombres de clase. La longitud total de los selectores anidados o BEM en los estilos tiene un efecto negativo en el tamaño de su aplicación. Actualmente, está lleno de herramientas que no generan estilos con selectores únicos: JSS, componentes con estilo, módulos CSS.

Minificación en casa


Todos estamos familiarizados con html, pero pocos han pensado que esto es solo una simple abstracción sobre un árbol de objetos muy complejos. La cadena de herencia para el elemento div es la siguiente:

HTMLDivElement -> HTMLElement -> Element -> Node -> EventTarget

Y cada objeto en esta cadena tiene de 10 a 100 propiedades y métodos que consumen mucha memoria. Y toda esta riqueza debe ser tomada en cuenta por el motor DOM para construir la imagen que vemos. Por lo tanto, trate de no usar elementos en exceso en la casa.

Minificar HTML. Elimine todo lo que usa para formatear html al momento de escribir. El hecho es que los espacios que se usan al escribir código en el navegador también se convierten en objetos en el hogar:

TextNode -> Node -> EventTarget

Eliminar comentarios También son un elemento del hogar y consumen muchos recursos:

Comment -> CharacterData -> Node -> EventTarget

Usar motores de plantilla jsx puede ser una buena práctica. El hecho es que al compilarlo se convierte en código js nativo que no genera espacios, comentarios y nunca se equivoca al abrir y cerrar etiquetas.

Una mala práctica, incluso diría que es una pesadilla, es facebook.com . Aquí están los fragmentos html:

Fragmento de página HTML
 <!--  1 --> <div class=""> <div class="_42ef"> <div class="_25-w"> <div class="_17pg"> <div class="_1rwk"> <form class=" _129h"> <div class=" _3d2q _65tb _7c_r _4w79"> <div class="_5rp7"> <div class="_1p1t"> <div class="_1p1v" id="placeholder-77m1n" style="white-space: pre-wrap;">  ... </div> </div> </div> </div> <ul class="_1obb"> ...li... </ul> </form> </div> </div> </div> </div> </div> <!--  2 --> <div> <div> <div class="_3nd0"> <div class="_1mwp navigationFocus _395 _4c_p _5bu_ _34nd _21mu _5yk1" role="presentation" style="" id="js_u"> <div class="_5yk2" tabindex="-1"> <div class="_5rp7"> <div class="_1p1t" style=""> <div class="_1p1v" id="placeholder-6t6up" style="white-space: pre-wrap;">    ? </div> </div> <div class="_5rpb"> <div aria-autocomplete="list" aria-controls="js_1" aria-describedby="placeholder-6t6up" aria-multiline="true" class="notranslate _5rpu" contenteditable="true" data-testid="status-attachment-mentions-input" role="textbox" spellcheck="true" style="outline: none; user-select: text; white-space: pre-wrap; overflow-wrap: break-word;"> <div data-contents="true"> <div class="" data-block="true" data-editor="6t6up" data-offset-key="6b02n-0-0"> <div data-offset-key="6b02n-0-0" class="_1mf _1mj"> <span data-offset-key="6b02n-0-0"> <br data-text="true"> </span> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div> 


Como puede ver, se usa una anidación de diez elementos, pero esta anidación no funciona. El primer fragmento solo muestra el texto "Escribir un comentario ..." y los íconos, el segundo "¿Qué hay de nuevo?". Como resultado de un uso no racional del DOM, todo el rendimiento del motor de plantillas React simplemente se niega, y el sitio se convierte en uno de los más lentos que conozco.

Aplicación web progresiva


Archivo de manifiesto


PWA le permite usar su aplicación web como una aplicación nativa. Cuando habilita el soporte en el sitio, el botón para instalar su sitio en el dispositivo (Windows, Android, iOS) aparece en el menú del navegador, después de lo cual comienza a comportarse como nativo y funciona sin conexión, y todo esto pasa por alto las tiendas de aplicaciones.

Habilitar el soporte de PWA en el sitio es realmente muy simple. Es suficiente incluir un enlace al archivo de manifiesto en la página html. El archivo de manifiesto se puede generar en pwabuilder.com .

No me detendré en detalle sobre el proceso de conexión, porque Esta sección es digna de un artículo grande por separado y en el Habré ya hay algunos bastante buenos.

Trabajador de servicio


La configuración de PWA no termina al conectar el archivo de manifiesto, también es necesario conectar el ServiceWorker, que será responsable de trabajar sin conexión.

Un código de ejemplo se puede encontrar allí en pwabuilder.com :

 // This is the service worker with the Cache-first network const CACHE = "pwabuilder-precache"; const precacheFiles = [ /* Add an array of files to precache for your app */ ]; self.addEventListener("install", function (event) { console.log("[PWA Builder] Install Event processing"); console.log("[PWA Builder] Skip waiting on install"); self.skipWaiting(); event.waitUntil( caches.open(CACHE).then(function (cache) { console.log("[PWA Builder] Caching pages during install"); return cache.addAll(precacheFiles); }) ); }); // Allow sw to control of current page self.addEventListener("activate", function (event) { console.log("[PWA Builder] Claiming clients for current page"); event.waitUntil(self.clients.claim()); }); // If any fetch fails, it will look for the request in the cache and serve it from there first self.addEventListener("fetch", function (event) { if (event.request.method !== "GET") return; event.respondWith( fromCache(event.request).then( function (response) { // The response was found in the cache so we responde with it and update the entry // This is where we call the server to get the newest version of the // file to use the next time we show view event.waitUntil( fetch(event.request).then(function (response) { return updateCache(event.request, response); }) ); return response; }, function () { // The response was not found in the cache so we look for it on the server return fetch(event.request) .then(function (response) { // If request was success, add or update it in the cache event.waitUntil(updateCache(event.request, response.clone())); return response; }) .catch(function (error) { console.log("[PWA Builder] Network request failed and no cache." + error); }); } ) ); }); function fromCache(request) { // Check to see if you have it in the cache // Return response // If not in the cache, then return return caches.open(CACHE).then(function (cache) { return cache.match(request).then(function (matching) { if (!matching || matching.status === 404) { return Promise.reject("no-match"); } return matching; }); }); } function updateCache(request, response) { return caches.open(CACHE).then(function (cache) { return cache.put(request, response); }); } 

Como puede ver en el código, todas las respuestas del servidor se almacenan en caché, pero el caché no se usa en línea. Y comienzan a usarse cuando la conexión con el servidor desaparece. Por lo tanto, el usuario que navega por el sitio puede no notar la desaparición a corto plazo de Internet, e incluso si Internet ha desaparecido durante mucho tiempo, el usuario todavía tiene la oportunidad de moverse por los datos ya almacenados en caché.

El script anterior es simple, pero adecuado solo para páginas de destino y es solo un punto de partida para escribir un trabajador para una aplicación web más seria. Pero más sobre eso en la segunda parte de este artículo. Además, la tecnología es conveniente porque no interrumpe el trabajo en los navegadores antiguos, es decir. en los navegadores de nivel IE11, no necesita reescribir la lógica, el modo fuera de línea simplemente no funcionará en él.

Accesibilidad


Corrección de atributos para personas con necesidades especiales


Hay muy pocas personas con una salud perfecta, pero desafortunadamente hay muchas personas con mala salud, incluida la visión. Y para que sea más cómodo para estas personas usar su aplicación web, es suficiente seguir reglas bastante simples:

  • Usa suficientes colores contrastantes. Según las estadísticas del Ministerio de Salud, el 20% de las personas tienen problemas de visión. Los sitios con poco contraste solo complican sus vidas, y las personas sanas aumentan la fatiga.
  • Organizar el índice de tabulación. Le permite usar el sitio sin un mouse y dispositivos táctiles. La disposición adecuada de las transiciones utilizando el teclado simplifica enormemente el proceso de completar formularios.
  • Atributo de etiqueta Aria en enlaces. Permite a los lectores de pantalla leer texto dentro de un atributo.
  • El atributo alt en las imágenes. Similar al anterior. Además, mostrará texto si no es posible descargar la imagen.
  • El idioma del documento. Marque la etiqueta html con el atributo con language lang = "código de idioma". Esto ayudará a que las herramientas auxiliares se configuren correctamente para el trabajo.

Como puede ver, los requisitos son realmente pocos y fáciles de cumplir. Pero por alguna razón, la mayoría de los desarrolladores ignoran estas reglas, incluso cuando se trata de sitios especializados para personas con necesidades especiales.

Mejores prácticas


Separe la aplicación front-end de la aplicación del servidor


Primero, si todavía está renderizando html en el servidor, deje de hacerlo ya. . .

SPA . windows , , iOS . , . Spring Asp.Net , SPA . SPA , nginx.

imagen

. Nginx , , .

, HTTP/2, gzip, cache


, , Nginx. .

  • SSL. SSL , , , Nginx. Nginx Asp.Net Core , .
  • GZIP . .
  • Cache . Get, Head , .
  • .

- , , nginx nginxconfig.io .

SEO


html


, . Lighthouse .


, . , .

, , .

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


All Articles