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.
- Compactação ainda melhor
- Como ativar ADVANCED_OPTIMIZATIONS
- O que observar ao usar ADVANCED_OPTIMIZATIONS
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âmetrosnote
das funçõesdisplayNoteTitle()
eunusedFunction()
, 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 globalflowerNote
. - remoção de código morto;
A compilação com
ADVANCED_OPTIMIZATIONS
remove a funçãounusedFunction()
completamente, porque ela nunca é chamada no código. - inlining de função;
A compilação com
ADVANCED_OPTIMIZATIONS
substitui a chamada paradisplayNoteTitle()
com o únicoalert()
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 comADVANCED_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.