Compilação avançada

Visão geral

Usar o Closure Compiler 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 alcança mais compactação por ser mais agressiva nas formas de transformar código e renomear símbolos. No entanto, essa abordagem mais agressiva significa que você precisa ter 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.

Este tutorial ilustra 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 apresenta o conceito de extern: um símbolo que é definido em um código externo ao código processado pelo compilador.

Antes de ler este tutorial, você precisa conhecer o processo de compilação de JavaScript com uma das ferramentas do Closure Compiler, como o aplicativo compilador baseado em Java.

Observação sobre a terminologia: a flag de linha de comando --compilation_level aceita as abreviações mais usadas ADVANCED e SIMPLE, além das mais precisas ADVANCED_OPTIMIZATIONS e SIMPLE_OPTIMIZATIONS. Este documento usa a forma mais longa, mas os nomes podem ser usados de forma intercambiável na linha de comando.

  1. Compactação ainda melhor
  2. Como ativar ADVANCED_OPTIMIZATIONS
  3. O que observar ao usar ADVANCED_OPTIMIZATIONS
    1. Remoção de código que você quer manter
    2. Nomes de propriedades inconsistentes
    3. Compilar duas partes do 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 Closure Compiler reduz o tamanho do JavaScript renomeando variáveis locais. No entanto, há outros símbolos além das variáveis locais que podem ser abreviados, e há outras maneiras de reduzir o código além de renomear símbolos. A compilação com ADVANCED_OPTIMIZATIONS explora toda a gama de 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:

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 encurta totalmente o código para:

alert("Flowers");

Os dois scripts produzem um alerta com a leitura "Flowers", mas o segundo é muito menor.

O nível ADVANCED_OPTIMIZATIONS vai além do simples encurtamento 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 de 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 a função unusedFunction() completamente, porque ela nunca é chamada no código.

  • inlining de função;

    A compilação com ADVANCED_OPTIMIZATIONS substitui a chamada para displayNoteTitle() com o ú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 "inlining". Se a função fosse mais longa ou mais complicada, a inclusão dela poderia mudar o comportamento do código, mas o Closure Compiler determina que, nesse caso, a inclusão é segura e economiza espaço. A compilação com ADVANCED_OPTIMIZATIONS também in-line constantes e algumas variáveis quando determina que pode fazer isso com segurança.

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

Como ativar ADVANCED_OPTIMIZATIONS

Para ativar o ADVANCED_OPTIMIZATIONS no aplicativo Closure Compiler, inclua a flag de linha de comando --compilation_level ADVANCED_OPTIMIZATIONS, como no comando a seguir:

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

O que observar ao usar ADVANCED_OPTIMIZATIONS

A seguir, listamos alguns efeitos colaterais comuns de 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 Closure Compiler vai 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 Closure Compiler presume que esse código não é necessário.

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

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

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

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

Solução: mover as chamadas de função para o código processado pelo Closure Compiler

Você pode encontrar remoção de código indesejada se compilar apenas parte do código com o Closure Compiler. Por exemplo, você pode ter um arquivo de biblioteca que contém apenas definições de função e um arquivo HTML que inclui a biblioteca e contém o código que chama essas funções. Nesse caso, se você compilar o arquivo da biblioteca com ADVANCED_OPTIMIZATIONS, o Closure Compiler vai remover todas as funções da biblioteca.

A solução mais simples para esse problema é compilar as funções junto com a parte do programa que as chama. Por exemplo, o Closure Compiler não remove 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 Closure Compiler vê que ela é chamada.

Em outras palavras, é possível evitar a remoção de código indesejado incluindo o ponto de entrada do programa no código transmitido ao Closure Compiler. O ponto de entrada de um programa é o local no código em que ele começa a ser executado. Por exemplo, no programa de notas de flores da seção anterior, as três últimas linhas são executadas assim que o JavaScript é carregado no navegador. Esse é o ponto de entrada do programa. Para determinar qual código você precisa manter, o Closure Compiler começa nesse ponto de entrada e rastreia o fluxo de controle do programa dali em diante.

Solução: inclua externs para as funções que você quer expor

Mais informações sobre essa solução estão abaixo e na página sobre externs e exports (links em inglês).

Nomes de propriedades inconsistentes

A compilação do Closure Compiler nunca muda literais de string no seu 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 maneira diferente dependendo se o código acessa elas com uma string. Se você misturar referências de string a uma propriedade com referências de sintaxe de ponto, o Closure Compiler vai renomear 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 o seguinte 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 isto:

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 a myTitle na função displayNoteTitle não foi renomeada. Como resultado, a última instrução se refere a uma propriedade myTitle que não existe mais.

Solução: seja consistente nos nomes das propriedades

Essa solução é bem simples. Para qualquer tipo ou objeto, use exclusivamente a sintaxe de ponto ou strings entre aspas. Não misture as sintaxes, principalmente ao se referir à mesma propriedade.

Além disso, quando possível, prefira usar a sintaxe de ponto, porque ela oferece melhores verificações e otimizações. Use o acesso a propriedades de string entre aspas somente quando não quiser que o Closure Compiler faça a renomeação, como quando o nome vem de uma fonte externa, como JSON decodificado.

Compilar duas partes do código separadamente

Se você dividir o aplicativo em diferentes partes de código, talvez queira compilar as partes separadamente. No entanto, se dois blocos de código interagirem, isso poderá causar dificuldades. Mesmo que você consiga, a saída das duas execuções do Closure Compiler não será compatível.

Por exemplo, suponha que um aplicativo seja dividido em duas partes: uma parte que recupera dados e uma parte que os mostra.

Confira 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.'};
}

Confira o código para mostrar os dados:

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

Se você tentar compilar esses dois blocos de código separadamente, vai encontrar vários problemas. Primeiro, o Closure Compiler remove a função getData() pelos motivos descritos em Remoção do código que você quer manter. Segundo, o Closure Compiler produz um erro fatal ao processar o código que mostra 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 mostra os dados, ele trata getData como indefinido.

Solução: compile todo o código de uma página

Para garantir a compilação adequada, compile todo o código de uma página em uma única execução. O Closure Compiler pode aceitar vários arquivos e strings JavaScript como entrada. Assim, é possível transmitir o código da biblioteca e outros códigos juntos em uma única solicitação de compilação.

Observação:essa abordagem não funciona se você precisar misturar código compilado e não compilado. Consulte Referências quebradas entre código compilado e não compilado para 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 Closure 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 vai falhar depois que você compilar, porque ele ainda se refere ao nome da função antiga. Da mesma forma, referências no código compilado a símbolos definidos externamente podem ser alteradas pelo Closure Compiler.

O "código não compilado" inclui qualquer código transmitido para a função eval() como uma string. O Closure Compiler nunca altera literais de string no código. Portanto, ele não muda as strings transmitidas para instruções eval().

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 em cada lado. Para aproveitar ao máximo o Closure Compiler, é importante entender qual caso você tem.

Antes de continuar, familiarize-se com externs e exports.

Solução para chamar código externo de código compilado: compilação com externs

Se você usar um código fornecido à sua página por outro script, verifique se o Closure Compiler não renomeia suas referências aos símbolos definidos nessa biblioteca externa. Para fazer isso, inclua um arquivo com as externs da biblioteca externa na sua compilação. Isso informa ao Closure Compiler quais nomes você não controla e, portanto, não podem ser alterados. Seu código precisa usar os mesmos nomes do arquivo externo.

Exemplos comuns são APIs como a OpenSocial API e a API Google Maps. Por exemplo, se o código chamar a função OpenSocial opensocial.newDataRequest() sem as externs adequadas, o Closure Compiler vai transformar essa chamada em a.b().

Solução para "Chamada para código compilado de código externo: implementação de externs"

Se você tiver um código JavaScript que reutiliza como uma biblioteca, talvez queira usar o Closure Compiler para reduzir apenas a biblioteca, permitindo que o código não compilado chame funções na biblioteca.

A solução nessa situação é implementar um conjunto de externs que definem a API pública da sua biblioteca. Seu código vai fornecer definições para os símbolos declarados nesses externs. Isso significa todas as classes ou funções mencionadas pelas suas externs. Também pode significar que suas classes implementam interfaces declaradas nas externs.

Essas externs são úteis para outras pessoas também, não apenas para você. Os consumidores da sua biblioteca precisam incluir esses arquivos se estiverem compilando o código, já que a biblioteca representa um script externo do ponto de vista deles. Pense nas externs 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 os externs na compilação. Isso pode parecer incomum, já que geralmente pensamos em externs como "vindo de outro lugar", mas é necessário informar ao Closure Compiler quais símbolos você está expondo para que eles não sejam renomeados.

Uma observação importante é que você pode receber diagnósticos de "definição duplicada" sobre o código que define os símbolos externos. O Closure Compiler presume que qualquer símbolo nas externs está sendo fornecido por uma biblioteca externa e não consegue entender que você está fornecendo uma definição intencionalmente. Esses diagnósticos podem ser suprimidos com segurança, e a supressão pode ser considerada uma confirmação de que você está realmente cumprindo sua API.

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