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

Criar para a Web oferece um alcance sem igual. Seu aplicativo da Web está a um clique de distância e disponível em quase todos os dispositivos conectados: smartphones, tablets, 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 seja carregado o mais rápido possível: você otimiza o caminho de renderização crítico, comprime e armazena em cache os recursos de texto e agora está analisando os recursos de imagem, que geralmente representam a maioria dos bytes transferidos. O problema é que a otimização de imagens é difícil:

  • Determinar o formato apropriado (vetorial x raster)
  • Determinar os formatos de codificação ideais (jpeg, webp etc.)
  • Determinar as configurações de compactação corretas (com perda e sem perda)
  • Determinar quais metadados devem ser mantidos ou removidos
  • Crie várias variantes de cada um para cada tela + resolução DPR
  • Considere o tipo de rede, a velocidade e as preferências 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 negligenciamos. Os humanos não são bons em explorar o mesmo espaço de pesquisa repetidamente, especialmente quando muitas etapas estão envolvidas. Os computadores, por outro lado, se destacam nesses tipos de tarefas.

A resposta para uma estratégia de otimização boa e sustentável para imagens e outros recursos com propriedades semelhantes é simples: automação. Se você estiver ajustando seus recursos manualmente, estará fazendo isso errado: você vai esquecer, vai ficar preguiçoso ou outra pessoa vai cometer esse erro por você, com certeza.

A saga do desenvolvedor preocupado com a performance

A pesquisa no espaço de otimização de imagens tem duas fases distintas: build-time e run-time.

  • Algumas otimizações são intrínsecas ao próprio recurso, como selecionar o formato e o tipo de codificação adequados, ajustar as configurações de compressão para cada codificador, remover 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 solicita 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 de rede do cliente, as preferências do usuário e do aplicativo e assim por diante.

As ferramentas de build-time existem, mas podem ser aprimoradas. Por exemplo, há muita economia ao ajustar dinamicamente a configuração "qualidade" para cada imagem e cada formato de imagem, mas ainda não vi ninguém usá-la fora da pesquisa. Essa é uma área pronta para inovação, mas, para os fins deste post, vou deixar assim. Vamos nos concentrar na parte 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 se dirige à barra. Enquanto isso, o desenvolvedor preocupado com a performance na equipe está se preparando para uma longa noite:

  1. Para ter 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 os outros.
  2. Para conseguir a melhor qualidade visual, ela precisa gerar várias variantes de cada imagem em diferentes resoluções: 1x, 1,5x, 2x, 2,5x, 3x e talvez até algumas outras.
  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 de visualização diferentes.
  4. O ideal é que ela também queira oferecer uma experiência resiliente em que os usuários em redes mais lentas busquem automaticamente uma resolução mais baixa. Afinal, o tempo é o mais importante.
  5. O aplicativo também expõe alguns controles do usuário que afetam qual recurso de imagem precisa ser buscado. Isso também precisa ser levado em consideração.

Ah, e então o designer percebe que precisa mostrar uma imagem diferente com 100% de largura se o tamanho da janela de visualização for pequeno para otimizar a legibilidade. Isso significa que agora precisamos repetir o mesmo processo para mais um recurso e, em seguida, tornar a busca condicional no tamanho da janela de visualização. Eu mencionei que isso é difícil? Certo, vamos lá. O elemento picture nos ajudará bastante:

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

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

Infelizmente, o elemento picture não permite definir regras de comportamento com base no tipo ou na velocidade de conexão do cliente. No entanto, o algoritmo de processamento permite que o agente do usuário ajuste o recurso que ele extrai em alguns casos. Consulte a etapa 5. Só podemos 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 conta 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 o JavaScript, mas isso perde as otimizações do scanner de pré-carregamento oferecidas por picture. Humm…

Além dessas limitações, ele funciona. Pelo menos para este recurso específico. O desafio real e de longo prazo aqui é que não podemos esperar que o designer ou o desenvolvedor crie códigos como esse para todos os recursos. É um quebra-cabeças divertido na primeira tentativa, mas perde o apelo imediatamente depois disso. 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 modelo acima.

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

Respire fundo, suspenda sua descrença e 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 todos os mesmos recursos que a marcação de imagem muito mais longa acima, além de permitir que o desenvolvedor tenha controle total sobre como, quais e quando os recursos de imagem são buscados. A "magia" está na primeira linha que ativa o relatório de dicas do cliente e informa ao navegador para anunciar a proporção de pixels do dispositivo (DPR), a largura do viewport de layout (Viewport-Width) e a largura de exibição pretendida (Width) dos recursos para o servidor.

Com as dicas do 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 os bytes entregues ou outros critérios de seleção de recursos. Vamos encarar a realidade: eles nunca fizeram isso e não precisavam fazer. Além disso, o desenvolvedor também não precisa reescrever e expandir a marcação acima, porque a seleção de recursos real é negociada pelo cliente e pelo servidor.

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

Diagrama de negociação de dicas de cliente

O Chrome anuncia o suporte ao formato WebP pelo cabeçalho de solicitação Accept. O novo navegador Edge também anuncia suporte ao JPEG XR pelo cabeçalho Accept.

Os três próximos cabeçalhos de solicitação são os cabeçalhos de sugestão do cliente que anunciam a proporção de pixels do dispositivo do cliente (3x), a largura da janela de visualização do 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.

Não há mágica aqui. 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 se preocupa apenas com os requisitos de apresentação e é algo em que podemos confiar em qualquer designer e desenvolvedor para escrever, enquanto a pesquisa pelo espaço de otimização de imagens é adiada para computadores e agora é facilmente automatizada em escala. Lembra do desenvolvedor preocupado com a performance? Agora, o trabalho dela é escrever um serviço de imagem que possa aproveitar as dicas fornecidas e retornar a resposta adequada: ela pode usar qualquer idioma ou servidor que quiser ou deixar que um serviço de terceiros ou uma CDN faça isso em nome dela.

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

Lembra desse cara aí em cima? Com as dicas do cliente, a tag de imagem agora considera a DPR, a viewport e a largura sem nenhuma marcação adicional. Se você precisar adicionar a direção de arte, use a tag picture, como ilustramos acima. Caso contrário, todas as tags de imagem atuais ficaram muito mais inteligentes. As dicas do cliente aprimoram os elementos img e picture.

Como assumir o controle da seleção de recursos com o service worker

O ServiceWorker é, na verdade, um proxy do lado do cliente executado no navegador. Ele intercepta todas as solicitações enviadas 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])
    }
    ...
}
Dicas de cliente serviceWorker.

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

  • É possível reescrever os valores do cabeçalho de dicas de cliente definidos pelo user agent.
  • É possível anexar novos valores de cabeçalhos de dicas do cliente à solicitação.
  • Você pode reescrever o URL e apontar a solicitação de imagem para um servidor alternativo (por exemplo, CDN).
    • Você pode até mesmo mover os valores de sugestão dos cabeçalhos para o URL, se isso facilitar a implantação na sua infraestrutura.
  • É possível armazenar respostas em cache e definir a própria lógica para os recursos que serão veiculados.
  • É possível adaptar a resposta com base na conectividade dos usuários.
  • É possível contabilizar substituições de preferências de usuário e de apps.
  • Você pode fazer o que quiser.

O elemento picture fornece o controle de direção de arte necessário na marcação HTML. As dicas do cliente fornecem anotações sobre as 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. Essa é a Web extensível em ação.

Perguntas frequentes sobre as dicas de cliente

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

  2. Por que as dicas do cliente são ativadas? Queremos minimizar a sobrecarga de sites que não usam as dicas do cliente. Para ativar as dicas do 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 uma delas, o user agent vai anexar as dicas apropriadas a todas as solicitações de subrecurso. No futuro, poderemos oferecer um mecanismo adicional para manter essa preferência para uma origem específica, o que permitirá que as mesmas dicas sejam enviadas em solicitações de navegação.

  3. Por que precisamos de dicas do cliente se temos um ServiceWorker? O ServiceWorker não tem acesso a informações de layout, recurso e largura da viewport. 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 são integradas ao navegador para disponibilizar esses dados como parte da solicitação.

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

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

  6. E <insert my favorite hint>? O ServiceWorker permite que os desenvolvedores interceptem e modifiquem (por exemplo, adicione 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 ServiceWorker". As dicas "nativas" enviadas no Chrome (DPR, largura, Resource-Width) são implementadas no navegador porque uma implementação puramente baseada em SW atrasaria todas as solicitações de imagem.

  7. Onde posso saber mais, conferir mais demonstrações e o que mais? Confira o documento explicativo e abra um problema no GitHub se tiver feedback ou outras dúvidas.