Front End

10 mar, 2014

Anúncios assíncronos com imports do HTML

Publicidade

Scripts carregados de forma típica (<script src=”a.js”> </ script>) bloqueiam a renderização, o que é ruim para o desempenho. A solução para esse problema consiste em carregar os scripts de forma assíncrona. Essa é a técnica utilizada pelos melhores snippets de terceiros, por exemplo, o Google Analytics, o botão Tweet, o SDK do Facebook e o botão siga do Google+.

Anúncios são, provavelmente, o conteúdo de terceiros mais comum na web. Infelizmente, a maioria dos anúncios não pode ser carregada de forma assíncrona porque eles usam document.write. (Fazer document.write em um script assíncrono faz com que a página inteira seja apagada. Veremos isso nos exemplos abaixo). Alguns times (GhostWriter, PageSpeed) tentaram resolver o problema do document.write assíncrono, mas isso requer muito código e ainda assim existem exceções.

Em minhas investigações sobre o desempenho dos imports do HTML, descobri uma maneira de carregar os anúncios de forma assíncrona – mesmo anúncios que usam document.write. Vamos dar uma olhada em como os imports do HTML fazem o bloqueio, como tornar os imports do HTML assíncronos, os perigos de se fazer isso com document.write e a solução para fazer esse trabalho todo.

Imports do HTML bloqueiam a renderização

Imports do HTML bloqueiam a renderização se utilizados de maneira padrão. Isso é lamentável, especialmente tendo em conta que essa é uma adição recente ao HTML quando a importância de não bloquear a renderização é bem conhecida. A especificação dos imports do HTML ainda está em estágio de rascunho, por isso é possível que a forma com que eles carregam mude para assíncrono por padrão.

No entanto, os imports do HTML são tipicamente criados assim:

<link rel="import" href="import.php">

O conteúdo do documento HTML importado é inserido assim:

<div id=import-container></div>
<script>
var link = document.querySelector('link[rel=import]');
var content = link.import.querySelector('#imported-content');
document.getElementById('import-container').appendChild(content.cloneNode(true));
</script>

A própria tag LINK não bloqueia a renderização – o navegador sabe que o import pode ser baixado de forma assíncrona. Mas a renderização é bloqueada quando o navegador encontra a primeira tag SCRIPT seguindo a tag LINK. Esse comportamento é demonstrado na página sync.php de teste. Para tornar o bloqueio observável, a importação leva cinco segundos para baixar e, em seguida, o “IMPORTED CONTENT” rosa é exibido. O bloqueio de SCRIPT está no meio da página, então o primeiro parágrafo é renderizado, mas o último não é renderizado até cinco segundos depois. Isso demonstra que os imports do HTML bloqueiam a renderização.

Executando os exemplos: Atualmente, os imports do HTML só funcionam no Chrome Canary e você precisa ligar as seguintes flags no chrome://flags/: Experimental Web Platform features, Experimental JavaScript e HTML Imports.

Tornando os imports do HTML assíncronos

Não é muito difícil tornar os imports do HTML assíncronos, evitando assim o comportamento de bloqueio padrão. Em vez de usar a tag de LINK no markup, nós criamos a tag usando JavaScript:

var link = document.createElement('link');
link.rel = 'import';
link.onload = function() {
    var link = document.querySelector('link[rel=import]');
    var content = link.import.querySelector('#imported-content');
    document.getElementById('import-container').appendChild(content.cloneNode(true));
};
link.href = url;
document.getElementsByTagName('head')[0].appendChild(link);

A página de teste async.php mostra que utilizar o padrão assíncrono não causa bloqueio na renderização – o último parágrafo é renderizado imediatamente, após cinco segundos, vemos o “IMPORTED CONTENT” rosa do import do HTML. Isso mostra que o import do HTML pode ser usado sem o bloquear o processamento da página.

Imports do HTML com document.write – cuidado!

Isso é meio estranho e pode ser difícil de entender: imports do HTML têm o seu próprio documento HTML. MAS (aqui é a parte complexa) qualquer JavaScript dentro do import do HTML é executado no contexto da página principal. Pelo menos é assim que funciona no Chrome agora. A especificação não aborda essa questão.

Isso é importante porque alguns conteúdos de terceiros (especialmente os anúncios) usam document.write. Algumas pessoas podem pensar que um document.write dentro de um import do HTML seria escrito no documento do import do HTML. Mas esse não é o caso. Em vez disso, document refere-se ao documento da página principal. Isso pode produzir resultados surpreendentes (e ruins).

Como mostrado nas páginas de teste sync_docwrite.php e async_docwrite.php, quando o import do HTML contém um script que usa document.write, ele apaga o conteúdo da página principal. Se não estiver certo se o conteúdo importado usa document.write, então é arriscado usar imports do HTML. Ou não é?

Usando os imports do HTML com document.write de forma segura

Já que muitos conteúdos de terceiros (especialmente anúncios) usam document.write, há uma razão para fazê-los funcionar com os imports do HTML. No entanto, como os exemplos anteriores mostram, isso pode ter resultados desastrosos, porque quando o import do HTML faz document.write, ele na verdade se refere ao document da página principal e a apaga.

Há uma correção simples para contornar essa situação. Não podemos redefinir o document, mas PODEMOS redefinir document.write dentro do import do HTML:

// inside the HTML import
document.write = function(msg) {
    document.currentScript.ownerDocument.write(msg);
};

Com essa mudança, toda saída document.write dos scripts dentro do import do HTML vão para o document do import do HTML. Isso elimina o problema do import apagar a página principal. Essa correção é mostrada pelas páginas de teste sync_docwrite-overwrite.php e async_docwrite-override.php override.php.

(document.write) assíncrono em anúncios usando imports do HTML

Vamos ver essa técnica em ação. A página de teste async_ads.php inclui o show_ads.js do Google para carregar anúncios reais. A versão substituta do document.write também mostra a saída para a página para que você possa verificar o que está acontecendo. O document.write funciona e o anúncio é mostrado, mesmo ao ser carregado de forma assíncrona.

Essa é uma grande conquista, mas existem alguns grandes ressalvas:

  • Embora tenhamos substituído document.write, pode haver outro JavaScript no import do HTML que assume que está sendo executado no contexto da página principal (por exemplo, location.href, document.title).
  • Algumas pessoas acreditam que seria bom desativar document.write dentro de imports do HTML, caso que faria com que os anúncios não funcionassem.
  • Precisamos de um fallback como suporte para o crescimento dos imports do HTML. Isso é possível por meio da detecção do suporte para imports do HTML e revertendo para a técnica atual de (bloqueio) para anúncios.

Talvez a maior ressalva seja se é realista esperar que os proprietários de sites para façam isso. Eu não acho que a maioria dos sites adotará essa técnica, mas eu gosto de ter uma opção para fazer anúncios assíncronos para sites que estão dispostos a realizar o trabalho. No momento, os proprietários de sites não têm boas alternativas para o carregamento anúncios sem bloquear o seu próprio conteúdo em sua página. Conheço alguns sites que já carregaram os anúncios na parte inferior da página em uma div escondida e depois clonaram a div no topo quando terminaram, mas isso geralmente resulta em uma queda na receita publicitária, porque os anúncios carregam mais tarde. Usar os imports do HTML permite que o anúncio seja carregado no topo, para que possamos ter um comportamento assíncrono sem perda de receita publicitária.

O objetivo deste artigo é sugerir que encontremos uma maneira de resolver um dos maiores obstáculos de hoje para tornar as páginas web mais rápidas: anúncios. A especificação para os imports do HTML é um projeto em andamento e há apenas uma implementação, então ambos são susceptíveis a mudança. Minha esperança é que nós possamos tornar assíncronos os imports do HTML por padrão para que eles não bloqueiem a renderização e possamos utilizar essa técnica para conseguir anúncios assíncronos.

***

Artigo traduzido pela Redação iMasters, com autorização do autor. Publicado originalmente em  http://www.stevesouders.com/blog/2013/11/16/async-ads-with-html-imports/