Compilação avançada

Visão geral

O uso do compilador de fechamento com um compilation_level de ADVANCED_OPTIMIZATIONS oferece taxas de compactação melhores do que a compilação com SIMPLE_OPTIMIZATIONS ou WHITESPACE_ONLY. A compilação com ADVANCED_OPTIMIZATIONS gera compactação extra sendo mais agressiva na forma de transformar código e renomear símbolos. No entanto, essa abordagem mais agressiva significa que você precisa tomar mais cuidado ao usar ADVANCED_OPTIMIZATIONS para garantir que o código de saída funcione da mesma forma que o código de entrada.

Neste tutorial, ilustramos o que o nível de compilação ADVANCED_OPTIMIZATIONS faz e o que você pode fazer para garantir que seu código funcione após a compilação com ADVANCED_OPTIMIZATIONS. Ele também introduz o conceito de extern: um símbolo definido em código externo ao código processado pelo compilador.

Antes de ler este tutorial, você precisa estar familiarizado com o processo de compilação do JavaScript com uma das ferramentas do Frontend Compiler (a IU do serviço do compilador, a API do serviço do compilador ou o aplicativo do compilador).

Observação sobre a terminologia: a sinalização de linha de comando --compilation_level é compatível com as abreviações mais usadas ADVANCED e SIMPLE, bem como as ADVANCED_OPTIMIZATIONS e SIMPLE_OPTIMIZATIONS mais precisas. Este documento usa a forma mais longa, mas os nomes podem ser usados como sinônimos na linha de comando.

  1. Compressão ainda melhor
  2. Como ativar ADVANCED_OPTIMIZATIONS
  3. Atenção ao usar ADVANCED_OPTIMIZATIONS
    1. Remoção de código que você quer manter
    2. Nomes de propriedade inconsistentes
    3. Como compilar duas partes de código separadamente
    4. Referências corrompidas entre código compilado e não compilado

Compactação ainda melhor

Com o nível de compilação padrão de SIMPLE_OPTIMIZATIONS, o Frontend Compiler torna o JavaScript menor renomeando as variáveis locais. Há símbolos diferentes das variáveis locais que podem ser encurtados, mas há formas de reduzir o código além de renomear símbolos. A compilação com ADVANCED_OPTIMIZATIONS explora todas as possibilidades de redução de código.

Compare as saídas de SIMPLE_OPTIMIZATIONS e ADVANCED_OPTIMIZATIONS para o seguinte código:

function unusedFunction(note) {
  alert(note['text']);
}

function displayNoteTitle(note) {
  alert(note['title']);
}

var flowerNote = {};
flowerNote['title'] = "Flowers";
displayNoteTitle(flowerNote);

A compilação com SIMPLE_OPTIMIZATIONS encurta o código para isso:

function unusedFunction(a){alert(a.text)}function displayNoteTitle(a){alert(a.title)}var flowerNote={};flowerNote.title="Flowers";displayNoteTitle(flowerNote);

A compilação com ADVANCED_OPTIMIZATIONS reduz totalmente o código para:

alert("Flowers");

Esses dois scripts produzem um alerta lendo "Flowers", mas o segundo script é muito menor.

O nível ADVANCED_OPTIMIZATIONS vai além do encurtamento simples de nomes de variáveis de várias maneiras, incluindo:

  • renomeação mais agressiva:

    A compilação com SIMPLE_OPTIMIZATIONS apenas renomeia os parâmetros note das funções displayNoteTitle() e unusedFunction(), porque essas são as únicas variáveis no script que são locais a uma função. ADVANCED_OPTIMIZATIONS também renomeia a variável global flowerNote.

  • remoção de código morto:

    A compilação com ADVANCED_OPTIMIZATIONS remove completamente a função unusedFunction(), porque ela nunca é chamada no código.

  • funcionalidade inline:

    A compilação por ADVANCED_OPTIMIZATIONS substitui a chamada para displayNoteTitle() pelo único alert() que compõe o corpo da função. Essa substituição de uma chamada de função pelo corpo da função é conhecida como "in-line". Se a função for mais longa ou mais complicada, a inserção in-line pode mudar o comportamento do código, mas o closure Compiler vai determinar que, nesse caso, ela é segura e economiza espaço. A compilação com ADVANCED_OPTIMIZATIONS também inclui constantes e algumas variáveis ao determinar se é possível fazer isso com segurança.

Essa lista é apenas uma amostra das transformações de redução de tamanho que a compilação ADVANCED_OPTIMIZATIONS pode executar.

Como ativar ADVANCED_OPTIMIZATIONS

A IU do serviço do Frontend Compiler, a API e o aplicativo têm métodos diferentes para definir compilation_level como ADVANCED_OPTIMIZATIONS.

Como ativar ADVANCED_OPTIMIZATIONS na IU do serviço do Compile Compiler

Para ativar o ADVANCED_OPTIMIZATIONS para a IU do serviço de fechamento de fechamento, clique no botão de opção "Avançado".

Como ativar ADVANCED_OPTIMIZATIONS na API service Compiler Compiler

Para ativar ADVANCED_OPTIMIZATIONS para a API de serviço do Compiler Compiler, inclua um parâmetro de solicitação chamado compilation_level com um valor ADVANCED_OPTIMIZATIONS, como no programa do Python a seguir:

#!/usr/bin/python2.4

import httplib, urllib, sys

params = urllib.urlencode([
    ('code_url', sys.argv[1]),
    ('compilation_level', 'ADVANCED_OPTIMIZATIONS'),
    ('output_format', 'text'),
    ('output_info', 'compiled_code'),
  ])

headers = { "Content-type": "application/x-www-form-urlencoded" }
conn = httplib.HTTPSConnection('closure-compiler.appspot.com')
conn.request('POST', '/compile', params, headers)
response = conn.getresponse()
data = response.read()
print data
conn.close()

Como ativar ADVANCED_OPTIMIZATIONS no aplicativo Frontend Compiler

Para ativar ADVANCED_OPTIMIZATIONS no aplicativo Compile Compiler, inclua a sinalização de linha de comando --compilation_level ADVANCED_OPTIMIZATIONS, como no comando a seguir:

java -jar compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --js hello.js

Atenção: ao usar ADVANCED_OPTIMIZATIONS

Veja abaixo alguns efeitos comuns e não intencionais do ADVANCED_OPTIMIZATIONS e as etapas que você pode seguir para evitá-los.

Remoção do código que você quer manter

Se você compilar apenas a função abaixo com ADVANCED_OPTIMIZATIONS, o Frontend Compiler produzirá uma saída vazia:

function displayNoteTitle(note) {
  alert(note['myTitle']);
}

Como a função nunca é chamada no JavaScript que você transmite ao compilador, o Frontend Compiler considera que esse código não é necessário.

Em muitos casos, isso é exatamente o que você quer. Por exemplo, se você compilar o código junto com uma grande biblioteca, o Frontend Compiler poderá determinar quais funções dessa biblioteca são realmente usadas e descartar as que você não usa.

No entanto, se você descobrir que o closure Compiler está removendo funções que querem manter, há duas maneiras de evitar isso:

  • Mova as chamadas de função para o código processado pelo closure Compiler.
  • Inclua funções externas para as funções que você quer expor.

As próximas seções discutem cada opção com mais detalhes.

Solução: mova suas chamadas de função para o código processado pelo compilador de fechamento

Talvez você encontre uma remoção de código indesejada se compilar apenas parte do código com o Compile Compiler. Por exemplo, é possível ter um arquivo de biblioteca que contenha apenas definições de função, e um arquivo HTML que inclua a biblioteca e que contenha o código que chama essas funções. Nesse caso, se você compilar o arquivo da biblioteca com ADVANCED_OPTIMIZATIONS, o Frontend Compiler removerá todas as suas funções de biblioteca.

A solução mais simples para esse problema é compilar suas funções com a parte do programa que as chama. Por exemplo, o Frontend Compiler não removerá displayNoteTitle() ao compilar o seguinte programa:

function displayNoteTitle(note) {
  alert(note['myTitle']);
}
displayNoteTitle({'myTitle': 'Flowers'});

A função displayNoteTitle() não é removida nesse caso porque o Close Compiler detecta que ela foi chamada.

Em outras palavras, é possível impedir a remoção de código indesejado incluindo o ponto de entrada do seu programa no código que você passar para o Compile Compiler. O ponto de entrada de um programa é o lugar no código em que o programa começa a ser executado. Por exemplo, no programa de notas de flores da seção anterior, as últimas três linhas são executadas assim que o JavaScript é carregado no navegador. Esse é o ponto de entrada para este programa. Para determinar qual código você precisa manter, o Frontend Compiler começa neste ponto de entrada e rastreia o fluxo de controle do programa a partir dali.

Solução: incluir funções externas que você queira expor

Veja mais informações sobre essa solução abaixo e na página sobre extensões e exportações.

Nomes de propriedade inconsistentes

A compilação do Frontend Compiler nunca muda literais de string no código, não importa o nível de compilação usado. Isso significa que a compilação com ADVANCED_OPTIMIZATIONS trata as propriedades de forma diferente, dependendo do acesso do código ao string. Se você misturar as referências de string a uma propriedade com referências de sintaxe de pontos, o Frontend Compiler renomeia algumas das referências a essa propriedade, mas não outras. Como resultado, seu código provavelmente não será executado corretamente.

Por exemplo, considere este código:

function displayNoteTitle(note) {
  alert(note['myTitle']);
}
var flowerNote = {};
flowerNote.myTitle = 'Flowers';

alert(flowerNote.myTitle);
displayNoteTitle(flowerNote);

As duas últimas instruções nesse código-fonte fazem exatamente a mesma coisa. No entanto, ao compactar o código com ADVANCED_OPTIMIZATIONS, você recebe o seguinte:

var a={};a.a="Flowers";alert(a.a);alert(a.myTitle);

A última instrução no código compactado produz um erro. A referência direta à propriedade myTitle foi renomeada como a, mas a referência entre aspas de myTitle na função displayNoteTitle não foi renomeada. Como resultado, a última instrução se refere a uma propriedade myTitle que não está mais lá.

Solução: mantenha a consistência nos nomes das propriedades

Essa solução é muito simples. Para qualquer tipo ou objeto, use sintaxe de ponto ou strings entre aspas exclusivamente. Não misture as sintaxes, especialmente em referência à mesma propriedade.

Além disso, quando possível, prefira usar a sintaxe de ponto, porque ela possibilita verificações e otimizações melhores. Use o acesso à propriedade de string entre aspas somente quando você não quiser que o Frontend Compiler faça uma renomeação, por exemplo, quando o nome vem de uma fonte externa, como um JSON decodificado.

Como compilar duas partes de código separadamente

Se você dividir seu aplicativo em diferentes pedaços de código, convém compilar os pedaços separadamente. No entanto, se duas partes do código interagem de alguma forma, isso pode causar dificuldade. Mesmo que você consiga, a saída das duas execuções do Frontend Compiler não será compatível.

Por exemplo, suponha que um aplicativo seja dividido em duas partes: uma que recupera dados e outra que exibe dados.

Veja o código para recuperar os dados:

function getData() {
  // In an actual project, this data would be retrieved from the server.
  return {title: 'Flower Care', text: 'Flowers need water.'};
}

Veja o código para exibir os dados:

var displayElement = document.getElementById('display');
function displayData(parent, data) {
  var textElement = document.createTextNode(data.text);
  parent.appendChild(textElement);
}
displayData(displayElement, getData());

Se tentar compilar esses dois pedaços de código separadamente, você vai encontrar vários problemas. Primeiro, o closure Compiler remove a função getData() pelos motivos descritos em Remoção de código que você quer manter. Em segundo lugar, o Frontend Compiler produz um erro fatal ao processar o código que exibe os dados.

input:6: ERROR - variable getData is undefined
displayData(displayElement, getData());

Como o compilador não tem acesso à função getData() ao compilar o código que exibe os dados, ele trata getData como indefinido.

Solução: compilar todo o código de uma página em conjunto

Para garantir a compilação adequada, compile todo o código de uma página em uma única execução de compilação. O Frontend Compiler pode aceitar vários arquivos JavaScript e strings JavaScript como entrada, para que você possa transmitir o código da biblioteca e outros códigos em uma única solicitação de compilação.

Observação: essa abordagem não funcionará se você precisar combinar códigos compilados e não compilados. Consulte Referências corrompidas entre código compilado e não compilado para ver dicas sobre como lidar com essa situação.

Referências corrompidas entre código compilado e não compilado

A renomeação de símbolos em ADVANCED_OPTIMIZATIONS vai interromper a comunicação entre o código processado pelo Frontend Compiler e qualquer outro código. A compilação renomeia as funções definidas no código-fonte. Qualquer código externo que chame suas funções será interrompido depois da compilação, porque ainda se refere ao nome da função antiga. Da mesma forma, as referências no código compilado para símbolos definidos externamente podem ser alteradas pelo Compile Compiler.

Lembre-se de que "código não compilado" inclui qualquer código transmitido para a função eval() como uma string. O closure Compiler nunca altera os literais de string do código. Por isso, ele não muda as strings transmitidas para instruções eval().

Saiba que esses são problemas relacionados, mas distintos: manter a comunicação compilada para externa e manter a comunicação externa para compilada. Esses problemas separados têm uma solução comum, mas há nuances de cada lado. Para aproveitar ao máximo o Close Compiler, é importante entender qual é seu caso.

Antes de continuar, conheça as extensões e exportações.

Solução para chamar código externo em código compilado: compilação com modelos externos

Se você usar o código fornecido na sua página por outro script, precisará ter certeza de que o Frontend Compiler não renomeia as referências aos símbolos definidos nessa biblioteca externa. Para fazer isso, inclua um arquivo que contenha as externas para a biblioteca externa em sua compilação. Isso informará ao Frontend Compiler quais nomes você não controla e, portanto, não podem ser alterados. O código precisa usar os mesmos nomes do arquivo externo.

Exemplos comuns disso são APIs, como a API OpenSocial e a API Google Maps. Por exemplo, se seu código chamar a função opensocial.newDataRequest() do WebGL, sem as externas adequadas, o closure Compiler transformará essa chamada em a.b().

Solução para chamar um código compilado usando código externo: implementando Estagiários

Se você tiver um código JavaScript que pode ser reutilizado como uma biblioteca, use o Frontend Compiler para reduzir apenas a biblioteca e, ao mesmo tempo, permitir que código não compilado chame funções na biblioteca.

A solução nessa situação é implementar um conjunto de funções externas que definem a API pública da sua biblioteca. O código fornecerá definições para os símbolos declarados nessas funções externas. Isso significa quaisquer classes ou funções mencionadas acima. Também pode significar que suas classes implementem interfaces declaradas nas funções externas.

Essas funções externas são úteis para outras pessoas, não só para você. Os consumidores da sua biblioteca precisarão incluí-los se estiverem compilando o código, já que a biblioteca representa um script externo da perspectiva. Pense nas externas como o contrato entre você e seus consumidores, ambos precisam de uma cópia.

Para isso, verifique se, ao compilar o código, você também inclui as externas na compilação. Isso pode parecer incomum, já que muitas vezes pensamos em "externas a partir de algum outro lugar", mas é necessário informar ao Close Compiler quais símbolos você está expondo, para que eles não sejam renomeados.

Uma restrição importante aqui é que você pode ter diagnósticos de "definição duplicada" sobre o código que define os símbolos externos. Esse recurso pressupõe que qualquer símbolo nas externas está sendo fornecido por uma biblioteca externa e não consegue entender se você está fornecendo uma definição de maneira intencional. Esses diagnósticos podem ser suprimidos com segurança, e é possível pensar na supressão como uma confirmação de que você está realmente cumprindo a API.

Além disso, o Frontend Compiler pode verificar se as suas definições correspondem aos tipos das declarações externas. Isso fornece mais uma confirmação de que suas definições estão corretas.