Automatiza la selección de recursos con sugerencias de clientes

Ilya Grigorik

Crear contenido para la Web te brinda un alcance sin precedentes. Tu aplicación web está a un clic de distancia y está disponible en casi todos los dispositivos conectados: smartphones, tablets, laptops, computadoras de escritorio, TVs y más, sin importar la marca o la plataforma. Para brindar la mejor experiencia, compilaste un sitio responsivo que adapta la presentación y la funcionalidad de cada factor de forma. Ahora estás ejecutando una lista de tareas de rendimiento a fin de garantizar que la aplicación se cargue lo más rápido posible. Optimizaste la ruta de acceso de renderización crítica, comprimiste y almacenaste en caché tus recursos de texto y ahora estás analizando los recursos de imagen, que suelen considerar la mayoría de los bytes. El problema es que la optimización de la imagen es difícil:

  • Determinar el formato adecuado (vector frente a trama)
  • Determinar los formatos de codificación óptimos (jpeg, webp, etcétera)
  • Determinar la configuración de compresión adecuada (con o sin pérdida)
  • Determina qué metadatos se deben conservar o quitar
  • Haz múltiples variantes de cada una para cada pantalla + resolución de DPR
  • ...
  • Ten en cuenta el tipo de red, la velocidad y las preferencias del usuario.

A nivel individual, estos son problemas bien comprendidos. En conjunto, crean un gran espacio de optimización que nosotros (los desarrolladores) a menudo pasamos por alto o descuidamos. Los seres humanos hacen un mal trabajo al explorar el mismo espacio de búsqueda de forma repetitiva, en especial cuando implican muchos pasos. Las computadoras, por otro lado, se destacan en este tipo de tareas.

La respuesta a una estrategia de optimización buena y sustentable para imágenes y otros recursos con propiedades similares es simple: automatización. Si ajustas manualmente tus recursos, lo haces mal: te olvidarás, serás perezoso o alguien más cometerá estos errores por ti, garantizado.

La saga de un desarrollador que se preocupa por el rendimiento

La búsqueda en el espacio de optimización de imágenes tiene dos fases distintas: tiempo de compilación y tiempo de ejecución.

  • Algunas optimizaciones son intrínsecas al recurso en sí; p.ej., seleccionar el formato y el tipo de codificación apropiados, ajustar la configuración de compresión para cada codificador, eliminar metadatos innecesarios, etcétera. Estos pasos se pueden realizar en el momento de la compilación.
  • Otras optimizaciones se determinan según el tipo y las propiedades del cliente que las solicita, y deben realizarse en "tiempo de ejecución": seleccionar el recurso adecuado para la DPR del cliente y el ancho de pantalla previsto, teniendo en cuenta la velocidad de la red del cliente, las preferencias del usuario y de la aplicación, etcétera.

Las herramientas de tiempo de compilación existen, pero se podrían mejorar. Por ejemplo, se pueden obtener muchos ahorros si se ajusta dinámicamente la configuración de "calidad" para cada imagen y cada formato de imagen, pero todavía no veo que alguien la use fuera de la investigación. Esta es un área lista para la innovación, pero para los propósitos de esta publicación, lo dejaré aquí. Enfoquémonos en la parte del tiempo de ejecución de la historia.

<img src="/image/thing" sizes="50vw"
        alt="image thing displayed at 50% of viewport width">

El intent de la aplicación es muy simple: recupera y muestra la imagen en el 50% del viewport del usuario. Aquí es donde casi todos los diseñadores se lavan las manos y la cabeza para el bar. Mientras tanto, el desarrollador del equipo que se preocupa por el rendimiento tiene una larga noche:

  1. Para obtener la mejor compresión, desea usar el formato de imagen óptimo para cada cliente: WebP para Chrome, JPEG XR para Edge y JPEG para el resto.
  2. Para obtener la mejor calidad visual, necesita generar múltiples variantes de cada imagen en diferentes resoluciones: 1x, 1.5x, 2x, 2.5x, 3x y tal vez algunas más.
  3. Para evitar entregar píxeles innecesarios, necesita comprender qué significa "el 50% del viewport del usuario", ya que existen muchos anchos de viewports diferentes.
  4. Idealmente, también quiere ofrecer una experiencia resistente en la que los usuarios de redes más lentas recuperen automáticamente una resolución más baja. Después de todo, llegó el momento de empezar.
  5. La aplicación también expone algunos controles del usuario que afectan qué recurso de imagen se debe recuperar, por lo que también debes tener esto en cuenta.

Además, el diseñador se da cuenta de que necesita mostrar una imagen diferente con un ancho del 100% si el tamaño del viewport es pequeño para optimizar la legibilidad. Esto significa que debemos repetir el mismo proceso para un elemento más y, luego, hacer que la recuperación sea condicional en el tamaño del viewport. ¿Les dije que esto es difícil? Bueno, comencemos. El elemento picture nos llevará bastante lejos:

<picture>
    <!-- serve WebP to Chrome and Opera -->
    <source
    media="(min-width: 50em)"
    sizes="50vw"
    srcset="/image/thing-200.webp 200w, /image/thing-400.webp 400w,
        /image/thing-800.webp 800w, /image/thing-1200.webp 1200w,
        /image/thing-1600.webp 1600w, /image/thing-2000.webp 2000w"
    type="image/webp">
    <source
    sizes="(min-width: 30em) 100vw"
    srcset="/image/thing-crop-200.webp 200w, /image/thing-crop-400.webp 400w,
        /image/thing-crop-800.webp 800w, /image/thing-crop-1200.webp 1200w,
        /image/thing-crop-1600.webp 1600w, /image/thing-crop-2000.webp 2000w"
    type="image/webp">
    <!-- serve JPEGXR to Edge -->
    <source
    media="(min-width: 50em)"
    sizes="50vw"
    srcset="/image/thing-200.jpgxr 200w, /image/thing-400.jpgxr 400w,
        /image/thing-800.jpgxr 800w, /image/thing-1200.jpgxr 1200w,
        /image/thing-1600.jpgxr 1600w, /image/thing-2000.jpgxr 2000w"
    type="image/vnd.ms-photo">
    <source
    sizes="(min-width: 30em) 100vw"
    srcset="/image/thing-crop-200.jpgxr 200w, /image/thing-crop-400.jpgxr 400w,
        /image/thing-crop-800.jpgxr 800w, /image/thing-crop-1200.jpgxr 1200w,
        /image/thing-crop-1600.jpgxr 1600w, /image/thing-crop-2000.jpgxr 2000w"
    type="image/vnd.ms-photo">
    <!-- serve JPEG to others -->
    <source
    media="(min-width: 50em)"
    sizes="50vw"
    srcset="/image/thing-200.jpg 200w, /image/thing-400.jpg 400w,
        /image/thing-800.jpg 800w, /image/thing-1200.jpg 1200w,
        /image/thing-1600.jpg 1600w, /image/thing-2000.jpg 2000w">
    <source
    sizes="(min-width: 30em) 100vw"
    srcset="/image/thing-crop-200.jpg 200w, /image/thing-crop-400.jpg 400w,
        /image/thing-crop-800.jpg 800w, /image/thing-crop-1200.jpg 1200w,
        /image/thing-crop-1600.jpg 1600w, /image/thing-crop-2000.jpg 2000w">
    <!-- fallback for browsers that don't support picture -->
    <img src="/image/thing.jpg" width="50%">
</picture>

Nos encargamos de la dirección artística y la selección de formato, y proporcionamos seis variantes de cada imagen para considerar la variabilidad de la DPR y el ancho del viewport del dispositivo del cliente. ¡Impresionante!

Lamentablemente, el elemento picture no nos permite definir ninguna regla sobre cómo debe comportarse según el tipo de conexión o la velocidad del cliente. Sin embargo, su algoritmo de procesamiento permite que el usuario-agente ajuste los recursos que recupera en algunos casos (consulta el paso 5). Solo tendremos que esperar que el usuario-agente sea lo suficientemente inteligente. (Nota: Ninguna de las implementaciones actuales lo es). Del mismo modo, no hay hooks en el elemento picture para permitir la lógica específica de la app que tiene en cuenta las preferencias de la app o del usuario. Para obtener estos dos últimos bits, tendríamos que trasladar toda la lógica anterior a JavaScript, pero eso perderá las optimizaciones del escáner de precarga que ofrece picture. Mmmm.

Más allá de esas limitaciones, funciona. Bueno, al menos para este activo en particular. El desafío real y a largo plazo es que no podemos esperar que el diseñador o el desarrollador diseñen manualmente un código como este para todos los recursos. Es un juego de ingenio divertido en el primer intento, pero pierde su atractivo inmediatamente después de eso. Necesitamos automatización. Tal vez el IDE o alguna otra herramienta de transformación de contenido puedan salvarnos y generar automáticamente el código estándar anterior.

Cómo automatizar la selección de recursos con sugerencias de clientes

Respira profundo, suspende la incredulidad y considera el siguiente ejemplo:

<meta http-equiv="Accept-CH" content="DPR, Viewport-Width, Width">
...
<picture>
    <source media="(min-width: 50em)" sizes="50vw" srcset="/image/thing">
    <img sizes="100vw" src="/image/thing-crop">
</picture>

Aunque no lo creas, el ejemplo anterior es suficiente para ofrecer las mismas capacidades que el lenguaje de marcado de imágenes mucho más largo que se menciona arriba. Además, como veremos, permite que el desarrollador controle cómo, qué y cuándo se recuperan los recursos de imagen. El signo "mágico" se encuentra en la primera línea que habilita los informes de sugerencias de cliente y le indica al navegador que anuncie al servidor la proporción de píxeles del dispositivo (DPR), el ancho de la viewport de diseño (Viewport-Width) y el ancho de la pantalla deseado (Width) de los recursos.

Con las sugerencias de cliente habilitadas, el lenguaje de marcado del cliente resultante conserva solo los requisitos de presentación. El diseñador no tiene que preocuparse por los tipos de imagen, las resoluciones de cliente, los puntos de interrupción óptimos para reducir los bytes entregados ni otros criterios de selección de recursos. Seamos realistas, nunca lo hicieron y no deberían tener que hacerlo. Mejor, el desarrollador tampoco necesita volver a escribir ni expandir el lenguaje de marcado anterior, ya que el cliente y el servidor negocian la selección real de recursos.

Chrome 46 proporciona compatibilidad nativa para las sugerencias DPR, Width y Viewport-Width. Las sugerencias están inhabilitadas de forma predeterminada, y el <meta http-equiv="Accept-CH" content="..."> anterior sirve como un indicador de aceptación que le indica a Chrome que agregue los encabezados especificados a las solicitudes salientes. Con esto en su lugar, examinemos los encabezados de solicitud y respuesta para una solicitud de imagen de muestra:

Diagrama de negociación de sugerencias para clientes

Chrome anuncia su compatibilidad con el formato WebP a través del encabezado de solicitud Accept; el nuevo navegador Edge también anuncia la compatibilidad con JPEG XR a través del encabezado Accept.

Los siguientes tres encabezados de solicitud son los encabezados de sugerencias del cliente que anuncian la proporción de píxeles del dispositivo del cliente (3x), el ancho del viewport del diseño (460 px) y el ancho de la pantalla deseado del recurso (230 px). Esto proporciona toda la información necesaria al servidor para seleccionar la variante de imagen óptima según su propio conjunto de políticas: disponibilidad de recursos pregenerados, costo de recodificar o cambiar el tamaño de un recurso, la popularidad de un recurso, la carga actual del servidor, etcétera. En este caso particular, el servidor usa las sugerencias DPR y Width, y muestra un recurso WebP, como se indica en los encabezados Content-Type, Content-DPR y Vary.

No hay magia aquí. Movimos la selección de recursos del lenguaje de marcado HTML a la negociación de solicitud y respuesta entre el cliente y el servidor. Como resultado, el HTML solo se refiere a los requisitos de presentación y es algo en lo que podemos confiar en que cualquier diseñador y desarrollador escriba, mientras que la búsqueda en el espacio de optimización de imágenes se difiere a las computadoras y ahora se automatiza con facilidad a gran escala. ¿Recuerdas a nuestro desarrollador responsable del rendimiento? Su trabajo ahora es escribir un servicio de imágenes que pueda aprovechar las sugerencias proporcionadas y mostrar la respuesta adecuada: puede usar cualquier lenguaje o servidor que desee o permitir que un servicio de terceros o una CDN lo haga en su nombre.

<img src="/image/thing" sizes="50vw"
        alt="image thing displayed at 50% of viewport width">

¿Recuerdas a este colega? Con las sugerencias de cliente, la humilde etiqueta de imagen ahora reconoce la DPR, el viewport y el ancho sin ningún lenguaje de marcado adicional. Si necesitas agregar una dirección artística, puedes usar la etiqueta picture, como se ilustra más arriba. De lo contrario, todas tus etiquetas de imagen existentes ahora son mucho más inteligentes. Las sugerencias de clientes mejoran los elementos img y picture existentes.

Controla la selección de recursos con un service worker

En efecto, ServiceWorker es un proxy del cliente que se ejecuta en tu navegador. Intercepta todas las solicitudes salientes y te permite inspeccionar, reescribir, almacenar en caché y sintetizar respuestas. Las imágenes no son diferentes y, cuando las sugerencias de clientes están habilitadas, el ServiceWorker activo puede identificar las solicitudes de imágenes, inspeccionar las sugerencias de clientes proporcionadas y definir su propia lógica de procesamiento.

self.onfetch = function(event) {
    var req = event.request.clone();
    console.log("SW received request for: " + req.url)
    for (var entry of req.headers.entries()) {
    console.log("\t" + entry[0] +": " + entry[1])
    }
    ...
}
ServiceWorker de sugerencias de cliente

ServiceWorker te brinda control total del cliente sobre la selección de recursos. Esto es fundamental. Déjate cuenta porque las posibilidades son casi infinitas:

  • Puedes reescribir los valores de encabezado de las sugerencias de cliente que estableció el usuario-agente.
  • Puedes agregar valores de encabezado de sugerencias de cliente nuevos a la solicitud.
  • Puedes reescribir la URL y dirigir la solicitud de imagen a un servidor alternativo (p.ej., CDN).
    • Incluso puedes mover los valores de sugerencia de los encabezados a la URL misma si eso facilita la implementación en tu infraestructura.
  • Puedes almacenar respuestas en caché y definir una lógica propia para la que se entregan los recursos.
  • Puedes adaptar tu respuesta en función de la conectividad de los usuarios.
  • Puede tener en cuenta las anulaciones de preferencias de aplicación y usuario.
  • Puedes... hacer lo que desees, en realidad.

El elemento picture proporciona el control necesario de dirección artística en el lenguaje de marcado HTML. Las sugerencias de cliente proporcionan anotaciones en las solicitudes de imágenes resultantes que permiten la automatización de la selección de recursos. ServiceWorker proporciona capacidades de administración de solicitudes y respuestas en el cliente. Esta es la Web extensible en acción.

Preguntas frecuentes sobre las sugerencias para el cliente

  1. ¿Dónde están disponibles las sugerencias de clientes? Se envía en Chrome 46. Se tienen en cuenta en Firefox y Edge.

  2. ¿Por qué se habilitan las sugerencias para clientes? Queremos minimizar la sobrecarga de los sitios que no utilizan sugerencias de clientes. Para habilitar las sugerencias de clientes, el sitio debe proporcionar el encabezado Accept-CH o la directiva <meta http-equiv> equivalente en el lenguaje de marcado de la página. Con cualquiera de esas dos opciones, el usuario-agente adjuntará las sugerencias adecuadas a todas las solicitudes de los subrecursos. En el futuro, es posible que proporcionemos un mecanismo adicional a fin de conservar esta preferencia para un origen en particular, lo que permitirá que se entreguen las mismas sugerencias en las solicitudes de navegación.

  3. ¿Por qué necesitamos sugerencias de clientes si tenemos ServiceWorker? ServiceWorker no tiene acceso a la información de diseño, recurso ni ancho del viewport. Al menos, no sin introducir costosos recorridos de ida y vuelta y retrasar significativamente la solicitud de imagen (p.ej., cuando el analizador de precarga inicia una solicitud de imagen). Las sugerencias de clientes se integran con el navegador para que estos datos estén disponibles como parte de la solicitud.

  4. ¿Las sugerencias de clientes son solo para recursos de imagen? El caso de uso principal detrás de las sugerencias de DPR, Viewport-Width y Width es habilitar la selección de recursos para los recursos de imagen. Sin embargo, se entregan las mismas sugerencias para todos los subrecursos, independientemente del tipo. Por ejemplo, las solicitudes de CSS y JavaScript también obtienen la misma información y se pueden usar para optimizar esos recursos.

  5. Algunas solicitudes de imágenes no informan el ancho, ¿por qué? Es posible que el navegador no conozca el ancho de pantalla deseado porque el sitio depende del tamaño intrínseco de la imagen. Como resultado, se omite la sugerencia de ancho para estas solicitudes y para las solicitudes que no tienen "ancho de visualización", p.ej., un recurso de JavaScript. Para recibir sugerencias de ancho, asegúrate de especificar un valor de tamaño en las imágenes.

  6. ¿Qué pasa con <insert my favorite hint>? El service worker permite que los desarrolladores intercepten y modifiquen (p.ej., agregar encabezados nuevos) todas las solicitudes salientes. A modo de ejemplo, es fácil agregar información basada en NetInfo para indicar el tipo de conexión actual. Consulta “Informes de capacidades con ServiceWorker”. Las sugerencias "nativas" que se envían en Chrome (DPR, ancho y ancho de recurso) se implementan en el navegador porque una implementación pura basada en SW retrasaría todas las solicitudes de imágenes.

  7. ¿Dónde puedo obtener más información, ver más demostraciones y qué puedo hacer? Consulta el documento de explicación y no dudes en abrir un problema en GitHub si tienes comentarios o preguntas.