Como automatizar a seleção de recursos com dicas do cliente

Ilya Grigorik

Ao criar um app para a Web, você tem um alcance inigualável. Seu aplicativo da Web está a um clique de distância e está disponível em quase todos os dispositivos conectados: smartphones, tablets, laptops e computadores, TVs e muito mais, independentemente da marca ou da plataforma. Para oferecer a melhor experiência, você criou um site responsivo que adapta a apresentação e a funcionalidade para cada formato. Agora você está executando sua lista de verificação de desempenho para garantir que o aplicativo carregue o mais rápido possível: você otimizou o caminho crítico de renderização, compactou e armazenou os recursos de texto em cache e agora está analisando a maioria dos bytes de imagem, que geralmente consideram a maioria dos bytes. O problema é que a otimização das imagens é difícil:

  • Determinar o formato apropriado (vetor x rasterizado)
  • Determine os formatos de codificação ideais (jpeg, webp, etc.)
  • Determinar as configurações de compressão certas (com ou sem perda)
  • Determinar quais metadados devem ser mantidos ou removidos
  • Criar diversas variantes de cada tela para cada tela + resolução da DPR
  • ...
  • Considere o tipo, a velocidade e as preferências da rede do usuário

Individualmente, esses são problemas bem compreendidos. Coletivamente, eles criam um grande espaço de otimização que nós, os desenvolvedores, muitas vezes ignoramos ou negligenciamos. Os humanos fazem um trabalho ruim de explorar o mesmo espaço de pesquisa repetidamente, especialmente quando há muitas etapas envolvidas. Os computadores, por outro lado, são ótimos para esse tipo de tarefa.

A resposta para uma estratégia de otimização boa e sustentável de imagens e outros recursos com propriedades semelhantes é simples: automação. Se você ajusta seus recursos manualmente, é um erro: você vai esquecer, vai ter preguiça ou outra pessoa vai cometer esses erros por você. Com certeza.

A saga do desenvolvedor focado no desempenho

A pesquisa por espaço de otimização de imagens tem duas fases distintas: tempo de build e tempo de execução.

  • Algumas otimizações são intrínsecas ao recurso, por exemplo, selecionar o formato e o tipo de codificação adequados, ajustar as configurações de compactação para cada codificador, eliminar metadados desnecessários e assim por diante. Essas etapas podem ser realizadas no "tempo de build".
  • Outras otimizações são determinadas pelo tipo e pelas propriedades do cliente que as solicitam e precisam ser realizadas no "tempo de execução": selecionando o recurso adequado para a DPR do cliente e a largura de exibição pretendida, considerando a velocidade da rede do cliente, as preferências do usuário e do aplicativo etc.

As ferramentas de tempo de build existem, mas poderiam ser melhores. Por exemplo, é possível economizar muito com o ajuste dinâmico da configuração de "qualidade" para cada imagem e cada formato de imagem, mas ainda não vi ninguém usar isso fora da pesquisa. Essa é uma área pronta para inovação, mas para os propósitos desta postagem, vou deixar isso assim. Vamos nos concentrar na parte do tempo de execução da história.

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

A intent do aplicativo é muito simples: buscar e mostrar a imagem em 50% da janela de visualização do usuário. É aqui que a maioria dos designers lava as mãos e a cabeça para o bar. Enquanto isso, o desenvolvedor da equipe focado no desempenho trabalha por uma longa noite:

  1. Para conseguir a melhor compactação, ela quer usar o formato de imagem ideal para cada cliente: WebP para Chrome, JPEG XR para Edge e JPEG para o restante.
  2. Para ter a melhor qualidade visual, ela precisa gerar diversas variantes de cada imagem em resoluções diferentes: 1x, 1,5x, 2x, 2,5x, 3x e talvez até algumas entre elas.
  3. Para evitar o envio de pixels desnecessários, ela precisa entender o que "50% da janela de visualização do usuário realmente significa". Existem muitas larguras de janela diferentes.
  4. O ideal é que ela também queira oferecer uma experiência resiliente em que os usuários em redes mais lentas buscam automaticamente uma resolução mais baixa. Afinal, é hora de vidro.
  5. O aplicativo também expõe alguns controles do usuário que afetam qual recurso de imagem precisa ser buscado, então eles também devem ser considerados.

Então, o designer percebe que precisa exibir uma imagem diferente a 100% de largura se o tamanho da janela de visualização for pequeno para otimizar a legibilidade. Isso significa que agora temos que repetir o mesmo processo para mais um recurso e, em seguida, tornar a busca condicional ao tamanho da janela de visualização. Já disse que isso é difícil? Bem, Ok, vamos lá. O elemento picture nos levará muito longe:

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

Processamos a direção de arte, a seleção de formato e fornecemos seis variantes de cada imagem para considerar a variabilidade na DPR e a largura da janela de visualização do dispositivo do cliente. Impressionante!

O elemento picture não permite definir regras de comportamento com base no tipo de conexão ou na velocidade do cliente. Dito isso, o algoritmo de processamento dele permite que o user agent ajuste o recurso buscado em alguns casos. Consulte a etapa 5. Temos que torcer para que o user agent seja inteligente o suficiente. Observação: nenhuma das implementações atuais é. Da mesma forma, não há hooks no elemento picture para permitir uma lógica específica do app que leve em consideração as preferências do app ou do usuário. Para conseguir esses dois últimos bits, teríamos que mover toda a lógica acima para JavaScript, mas isso perde as otimizações do scanner de pré-carregamento oferecidas por picture. Humm…

Deixando de lado essas limitações, ele funciona. Bem, pelo menos para este recurso específico. O verdadeiro desafio, e de longo prazo, é que não podemos esperar que o designer ou o desenvolvedor criem manualmente um código como esse para cada recurso. É um quebra-cabeça engraçado na primeira tentativa, mas perde o encanto imediatamente depois. Precisamos de automação. Talvez o ambiente de desenvolvimento integrado ou outras ferramentas de transformação de conteúdo possam nos salvar e gerar automaticamente o código boilerplate acima.

Como automatizar a seleção de recursos com dicas do cliente

Respire fundo, suspenda sua descrença e agora considere o seguinte exemplo:

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

Acredite ou não, o exemplo acima é suficiente para oferecer os mesmos recursos da marcação de imagem, muito maior, e, como veremos, isso permite controle total do desenvolvedor sobre como, quais e quando os recursos de imagem são buscados. A "mágica" está na primeira linha que ativa a geração de relatórios de dicas do cliente e instrui o navegador a anunciar a proporção de pixels do dispositivo (DPR), a largura da janela de visualização do layout (Viewport-Width) e a largura de exibição pretendida (Width) dos recursos para o servidor.

Com as dicas de cliente ativadas, a marcação resultante do lado do cliente mantém apenas os requisitos de apresentação. O designer não precisa se preocupar com tipos de imagem, resoluções de cliente, pontos de interrupção ideais para reduzir bytes entregues ou outros critérios de seleção de recursos. Sejamos francos, eles nunca tiveram, e não deveriam ter feito isso. Melhor ainda, o desenvolvedor também não precisa reescrever e expandir a marcação acima, porque a seleção real de recursos é negociada pelo cliente e pelo servidor.

O Chrome 46 oferece suporte nativo para as dicas DPR, Width e Viewport-Width. As dicas ficam desativadas por padrão, e o <meta http-equiv="Accept-CH" content="..."> acima serve como um sinal de ativação que instrui o Chrome a anexar os cabeçalhos especificados às solicitações de saída. Agora, vamos examinar os cabeçalhos de solicitação e resposta para um exemplo de solicitação de imagem:

Diagrama de negociação de dicas do cliente

O Chrome anuncia o suporte ao formato WebP por meio do cabeçalho de solicitação Accept. O novo navegador Edge anuncia a compatibilidade com JPEG XR usando o cabeçalho Accept.

Os próximos três cabeçalhos de solicitação são os cabeçalhos de dica do cliente que anunciam a proporção de pixels do dispositivo do cliente (3x), a largura da janela de visualização de layout (460 px) e a largura de exibição pretendida do recurso (230 px). Isso fornece todas as informações necessárias para que o servidor selecione a variante de imagem ideal com base no próprio conjunto de políticas: disponibilidade de recursos pré-gerados, custo de recodificação ou redimensionamento de um recurso, popularidade de um recurso, carga atual do servidor e assim por diante. Nesse caso específico, o servidor usa as dicas DPR e Width e retorna um recurso WebP, conforme indicado pelos cabeçalhos Content-Type, Content-DPR e Vary.

Isso não tem segredo. Movemos a seleção de recursos da marcação HTML para a negociação de solicitação/resposta entre o cliente e o servidor. Como resultado, o HTML está preocupado apenas com os requisitos de apresentação e é algo em que qualquer designer e desenvolvedor pode escrever, enquanto a pesquisa pelo espaço de otimização de imagens é adiada para computadores e agora é facilmente automatizada em escala. Lembra do nosso desenvolvedor focado no desempenho? Agora, o trabalho dela é criar um serviço de imagem que aproveite as dicas fornecidas e retorne a resposta adequada: ela pode usar qualquer linguagem ou servidor que quiser ou deixar um serviço de terceiros ou uma CDN fazer isso em nome dela.

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

Além disso, se lembra desse cara acima? Com dicas do cliente, a tag de imagem modesta agora reconhece DPR, janela de visualização e largura sem nenhuma marcação adicional. Se você precisar adicionar direção de arte, use a tag picture, conforme ilustrado acima. Caso contrário, todas as suas tags de imagem existentes ficaram muito mais inteligentes. As dicas do cliente melhoram os elementos img e picture.

Assumir o controle sobre a seleção de recursos com o service worker

O ServiceWorker é, na verdade, um proxy do lado do cliente em execução no seu navegador. Ele intercepta todas as solicitações de saída e permite inspecionar, reescrever, armazenar em cache e até mesmo sintetizar respostas. As imagens não são diferentes e, com as dicas de cliente ativadas, o ServiceWorker ativo pode identificar as solicitações de imagem, inspecionar as dicas de cliente fornecidas e definir a própria lógica de processamento.

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 dicas do cliente.

O ServiceWorker oferece controle total do lado do cliente sobre a seleção de recursos. Isso é fundamental. Deixe isso acontecer, porque as possibilidades são quase infinitas:

  • É possível reescrever os valores do cabeçalho de dicas do cliente definidos pelo user agent.
  • É possível anexar novos valores de cabeçalho de dicas de cliente à solicitação.
  • É possível reescrever o URL e apontar a solicitação de imagem para um servidor alternativo (por exemplo, CDN).
    • É possível até mesmo mover os valores de dica dos cabeçalhos para o próprio URL, caso isso facilite a implantação na sua infraestrutura.
  • É possível armazenar respostas em cache e definir a própria lógica para disponibilizar os recursos.
  • Você pode adaptar sua resposta de acordo com a conectividade dos usuários.
  • Você pode contabilizar as substituições de preferência do aplicativo e do usuário.
  • Você pode... fazer o que o seu coração desejar, na verdade.

O elemento picture fornece o controle necessário de direção de arte na marcação HTML. As dicas do cliente fornecem anotações nas solicitações de imagem resultantes que permitem a automação da seleção de recursos. O ServiceWorker oferece recursos de gerenciamento de solicitações e respostas no cliente. Vejamos a Web extensível em ação.

Perguntas frequentes sobre dicas do cliente

  1. Onde as dicas do cliente estão disponíveis? Enviado no Chrome 46. Em consideração no Firefox e no Edge.

  2. Por que as dicas do cliente estão ativadas? Queremos minimizar a sobrecarga de sites que não usam dicas do cliente. Para ativar as dicas de cliente, o site precisa fornecer o cabeçalho Accept-CH ou a diretiva <meta http-equiv> equivalente na marcação da página. Com qualquer um dos presentes, o user agent vai anexar as dicas apropriadas a todas as solicitações de recursos secundários. No futuro, poderemos fornecer um outro mecanismo para manter essa preferência em uma origem específica, o que permitirá que as mesmas dicas sejam entregues nas solicitações de navegação.

  3. Por que precisamos de dicas de clientes se temos o ServiceWorker? O ServiceWorker não tem acesso às informações de layout, recursos e largura da janela de visualização. Pelo menos, não sem introduzir idas e voltas caras e atrasar significativamente a solicitação de imagem, por exemplo, quando uma solicitação de imagem é iniciada pelo analisador de pré-carregamento. As dicas do cliente se integram ao navegador para disponibilizar esses dados como parte da solicitação.

  4. As dicas do cliente são apenas para recursos de imagem? O principal caso de uso por trás das dicas de DPR, largura da janela de visualização e largura é ativar a seleção de recursos para recursos de imagem. No entanto, as mesmas dicas são entregues para todos os sub-recursos, independentemente do tipo. Por exemplo, as solicitações de CSS e JavaScript também recebem as mesmas informações e também podem ser usadas para otimizar esses recursos.

  5. Algumas solicitações de imagem não informam a largura. Por quê? O navegador pode não saber a largura pretendida de exibição porque o site depende do tamanho intrínseco da imagem. Como resultado, a dica de largura é omitida para essas solicitações e para aquelas que não têm "largura de exibição", por exemplo, um recurso JavaScript. Para receber dicas de largura, especifique um valor de tamanho nas imagens.

  6. E quanto a <insert myfavorite hint>? O ServiceWorker permite que os desenvolvedores interceptem e modifiquem (por exemplo, adicionar novos cabeçalhos) todas as solicitações enviadas. Por exemplo, é fácil adicionar informações baseadas em NetInfo para indicar o tipo de conexão atual. Consulte Relatórios de recursos com o ServiceWorker. As dicas "nativas" enviadas no Chrome (DPR, Width, Resource-Width) são implementadas no navegador porque uma implementação pura baseada em SW atrasaria todas as solicitações de imagem.

  7. Onde posso saber mais, acessar mais demonstrações e o que fazer? Confira o documento de explicação e fique à vontade para abrir um problema no GitHub se tiver feedback ou outras dúvidas.