Automatiza la selección de recursos con sugerencias de clientes

Desarrollar 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 y computadoras de escritorio, TVs y mucho más, independientemente de la marca o la plataforma. Para brindar la mejor experiencia, creaste un sitio responsivo que adapta la presentación y la funcionalidad para cada factor de forma. Ahora, estás revisando tu lista de tareas de rendimiento para asegurarte de que la aplicación se cargue lo más rápido posible: optimizaste tu ruta de renderización crítica, comprimiste y almacenaste en caché tus recursos de texto y, ahora, estás analizando tus recursos de imagen, que a menudo representan la mayoría de los bytes transferidos. El problema es que la optimización de imágenes es difícil:

  • Determina el formato adecuado (vectorial o de trama)
  • Determina los formatos de codificación óptimos (jpeg, webp, etcétera).
  • Determina la configuración de compresión correcta (con pérdida o sin pérdida)
  • Determina qué metadatos se deben conservar o quitar
  • Crea varias variantes de cada una para cada pantalla y resolución de DPR.
  • Ten en cuenta el tipo de red, la velocidad y las preferencias del usuario

De forma individual, estos son problemas bien conocidos. En conjunto, crean un gran espacio de optimización que a menudo nosotros (los desarrolladores) olvidamos o descuidamos. Los humanos hacen un trabajo deficiente cuando exploran el mismo espacio de búsqueda de forma repetitiva, en especial cuando hay muchos pasos involucrados. Por otro lado, las computadoras se destacan en este tipo de tareas.

La respuesta a una buena estrategia de optimización sostenible para las imágenes y otros recursos con propiedades similares es simple: la automatización. Si estás ajustando tus recursos de forma manual, estás haciendo lo incorrecto: te olvidarás, te dará pereza o alguien más cometerá estos errores por ti, garantizado.

La saga del desarrollador preocupado por el rendimiento

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

  • Algunas optimizaciones son intrínsecas al recurso en sí, p.ej., seleccionar el formato y el tipo de codificación adecuados, ajustar la configuración de compresión para cada codificador, quitar metadatos innecesarios, etcétera. Estos pasos se pueden realizar en el “tiempo de compilación”.
  • Otras optimizaciones se determinan según el tipo y las propiedades del cliente que las solicita y deben realizarse en el "tiempo de ejecución": seleccionar el recurso adecuado para el 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 podrían mejorarse. Por ejemplo, se pueden obtener muchos ahorros si se ajusta de forma dinámica el parámetro de configuración “calidad” para cada imagen y cada formato de imagen, pero aún no veo a nadie que lo use fuera de la investigación. Esta es un área propicia para la innovación, pero, a los fines de esta publicación, me limitaré a eso. 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: recuperar y mostrar la imagen en el 50% de la ventana de visualización del usuario. Aquí es donde la mayoría de los diseñadores se lavan las manos y se dirigen al bar. Mientras tanto, el desarrollador del equipo que se preocupa por el rendimiento tendrá una larga noche:

  1. Para obtener la mejor compresión, quiere 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 varias variantes de cada imagen en diferentes resoluciones: 1x, 1.5x, 2x, 2.5x, 3x y, tal vez, algunas más en el medio.
  3. Para evitar entregar píxeles innecesarios, debe comprender qué significa “el 50% de la ventana de visualización del usuario”. Hay muchos anchos de ventana de visualización diferentes.
  4. Idealmente, también quiere ofrecer una experiencia resiliente en la que los usuarios de redes más lentas recuperen automáticamente una resolución más baja. Después de todo, es hora de usar el vidrio.
  5. La aplicación también expone algunos controles de usuario que afectan qué recurso de imagen se debe recuperar, por lo que también se debe tener en cuenta.

Además, la diseñadora se da cuenta de que necesita mostrar una imagen diferente al 100% de ancho si el tamaño de la vista previa es pequeño para optimizar la legibilidad. Esto significa que ahora debemos repetir el mismo proceso para un recurso más y, luego, hacer que la recuperación sea condicional en función del tamaño del viewport. ¿Te mencioné que esto es difícil? Muy bien, vamos a ello. 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 de arte, la selección de formatos y proporcionamos seis variantes de cada imagen para tener en cuenta la variabilidad en la DPR y el ancho de la vista del dispositivo del cliente. ¡Impresionante!

Lamentablemente, el elemento picture no nos permite definir reglas sobre cómo debe comportarse en función del tipo de conexión o la velocidad del cliente. Dicho esto, su algoritmo de procesamiento permite que el usuario-agente ajuste qué recurso 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 una lógica específica de la app que tenga en cuenta las preferencias de la app o del usuario. Para obtener estos dos últimos bits, tendríamos que mover toda la lógica anterior a JavaScript, pero eso pierde las optimizaciones del escáner de carga previa que ofrece picture. Mmmm.

Aparte 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 creen código como este de forma manual para cada recurso. Es un acertijo cerebral divertido al principio, pero pierde su atractivo inmediatamente después. Necesitamos automatización. Quizás el IDE o alguna otra herramienta de transformación de contenido nos pueda ayudar y generar automáticamente el modelo anterior.

Automatiza la selección de recursos con sugerencias de cliente

Respira profundo, suspende tu incredulidad y ahora 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>

Créalo o no, el ejemplo anterior es suficiente para proporcionar todas las mismas funciones que el marcado de imagen mucho más largo anterior y, como veremos, permite que el desarrollador tenga un control total sobre cómo, cuáles y cuándo se recuperan los recursos de imagen. La "magia" está en la primera línea que habilita los informes de sugerencias del cliente y le indica al navegador que anuncie al servidor la relación de píxeles del dispositivo (DPR), el ancho del viewport del diseño (Viewport-Width) y el ancho de pantalla previsto (Width) de los recursos.

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

Chrome 46 proporciona compatibilidad nativa con 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 habilitación que le indica a Chrome que adjunte los encabezados especificados a las solicitudes salientes. Con eso en mente, examinemos los encabezados de solicitud y respuesta de una solicitud de imagen de ejemplo:

Diagrama de negociación de sugerencias de cliente

Chrome anuncia su compatibilidad con el formato WebP a través del encabezado de solicitud Aceptar. Del mismo modo, el nuevo navegador Edge anuncia la compatibilidad con JPEG XR a través del encabezado Aceptar.

Los siguientes tres encabezados de solicitud son los encabezados de sugerencia del cliente que promocionan la proporción de píxeles del dispositivo del cliente (3x), el ancho de la ventana de visualización del diseño (460 px) y el ancho de visualización previsto del recurso (230 px). Esto le proporciona al servidor toda la información necesaria para seleccionar la variante de imagen óptima en función de su propio conjunto de políticas: disponibilidad de recursos generados previamente, costo de volver a codificar o cambiar el tamaño de un recurso, popularidad de un recurso, carga del servidor actual, etcétera. En este caso en particular, el servidor usa las sugerencias DPR y Width y muestra un recurso WebP, como lo indican los encabezados Content-Type, Content-DPR y Vary.

No hay magia aquí. Trasladamos la selección de recursos del marcado HTML a la negociación de solicitud-respuesta entre el cliente y el servidor. Como resultado, el HTML solo se preocupa por los requisitos de presentación y es algo que cualquier diseñador y desarrollador puede escribir, mientras que la búsqueda a través del espacio de optimización de imágenes se aplaza a las computadoras y ahora se automatiza fácilmente a gran escala. ¿Recuerdas a nuestro desarrollador preocupado por el 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 hagan en su nombre.

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

Además, ¿recuerdas a este tipo de arriba? Con las sugerencias de cliente, la humilde etiqueta de imagen ahora se adapta al DPR, al viewport y al ancho sin ningún marcado adicional. Si necesitas agregar art-direction, puedes usar la etiqueta picture, como se ilustró anteriormente. De lo contrario, todas tus etiquetas de imagen existentes se volvieron mucho más inteligentes. Las sugerencias del cliente mejoran los elementos img y picture existentes.

Cómo tomar el control de la selección de recursos con el trabajador de servicio

ServiceWorker es, en efecto, un proxy del cliente que se ejecuta en tu navegador. Intercepta todas las solicitudes salientes y te permite inspeccionar, reescribir, almacenar en caché y hasta sintetizar respuestas. Las imágenes no son diferentes y, con las sugerencias del cliente habilitadas, el ServiceWorker activo puede identificar las solicitudes de imagen, inspeccionar las sugerencias del cliente 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])
    }
    ...
}
Sugerencias del cliente serviceWorker.

ServiceWorker te brinda control total del cliente sobre la selección de recursos. Esto es fundamental. Piensa en eso, porque las posibilidades son casi infinitas:

  • Puedes reescribir los valores de encabezado de sugerencias de cliente que establece el usuario-agente.
  • Puedes agregar nuevos valores de encabezados de sugerencias del cliente a la solicitud.
  • Puedes volver a escribir 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 si eso facilita la implementación en tu infraestructura.
  • Puedes almacenar en caché las respuestas y definir tu propia lógica para los recursos que se entregan.
  • Puedes adaptar tu respuesta en función de la conectividad de los usuarios.
  • Puedes tener en cuenta las anulaciones de preferencias de la aplicación y del usuario.
  • Puedes hacer lo que quieras.

El elemento picture proporciona el control de dirección de arte necesario en el marcado HTML. Las sugerencias del cliente proporcionan anotaciones en las solicitudes de imágenes resultantes que habilitan 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 del cliente

  1. ¿Dónde están disponibles las sugerencias para el cliente? Se envió en Chrome 46. En estudio en Firefox y Edge.

  2. ¿Por qué se deben aceptar las sugerencias para el cliente? Queremos minimizar la sobrecarga para los sitios que no usarán sugerencias de cliente. Para habilitar las sugerencias para el cliente, el sitio debe proporcionar el encabezado Accept-CH o una directiva <meta http-equiv> equivalente en el lenguaje de marcado de la página. Con cualquiera de ellos presente, el usuario-agente agregará las sugerencias adecuadas a todas las solicitudes de subrecursos. En el futuro, es posible que proporcionemos un mecanismo adicional para conservar esta preferencia para un origen en particular, lo que permitirá que se entreguen los mismos indicadores en las solicitudes de navegación.

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

  4. ¿Las sugerencias del cliente son solo para recursos de imagen? El caso de uso principal detrás de DPR, Viewport-Width y Width hints 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 (p.ej., las solicitudes de CSS y JavaScript también obtienen la misma información y se pueden usar para optimizar esos recursos).

  5. ¿Por qué algunas solicitudes de imágenes no informan el ancho? Es posible que el navegador no conozca el ancho de visualización previsto porque el sitio se basa en el tamaño intrínseco de la imagen. Como resultado, se omite la sugerencia de ancho para esas solicitudes y para las 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 tus imágenes.

  6. ¿Qué sucede con <insert my favorite hint>? ServiceWorker permite a los desarrolladores interceptar y modificar (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, Width, Resource-Width) se implementan en el navegador porque una implementación basada únicamente en SW retrasaría todas las solicitudes de imagen.

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