Pular para o conteúdo principal

Primeiros Passos com Google Closure Compiler


Code review é o processo de análise de código por meio de um processo (teoricamente) rigoroso de leitura e revisão crítica de seu conteúdo feita por, no mínimo, duas pessoas. Antes de enviar o código para revisão, os programadores geralmente o "limpam" com uma série de ferramentas automatizadas, dependendo da linguagem e plataforma a serem usados.

No mundo JavaScript, simplesmente por causa da natureza da linguagem, muitos desenvolvedores, especialmente os iniciantes, não conseguem ver as armadilhas e erros que cometem ao programar. Pode ser qualquer coisa, desde o uso de variáveis ​​não declaradas a exceções de ponteiro nulo devido a verificações nulas inconsistentes, até mesmo o uso indevido — ou nenhum uso — do que uma função retorna.

O Google Closure Compiler faz exatamente isso: ele compila código JavaScript para um JavaScript melhorado, analisando, minimizando e reescrevendo-o. E, é claro, também nos alerta sobre as mesmas armadilhas que mencionamos acima. Ele remove o que é desnecessário, verifica a sintaxe, etc.

Neste artigo, apresentaremos alguns problemas comuns que os desenvolvedores de front-end enfrentam, bem como entender melhor como o Closure Compiler pode nos ajudar a verificar rapidamente o código que estamos escrevendo para garantir o melhor código possível.

Começando simples

Você pode executar o Closure Compiler na linha de comando da sua aplicação (por exemplo, em Node.js) ou por meio de um serviço web disponível gratuitamente.



Basicamente, ele expõe uma página web onde você pode compilar seu código por meio de um arquivo JavaScript linkado ou simplesmente colando o código. A ferramenta exibe os resultados no lado direito da tela.

Esses resultados, por sua vez, mostram a diferença de tamanho entre o código-fonte original e a versão compilada (compactada e descompactada) e um link gerado automaticamente para você fazer o download do arquivo JavaScript final.

Mais importante, você verá uma tabela com quatro guias, exibindo:

  • O código compilado final;
  • Uma lista de avisos e erros, indicando quando eles aconteceram, a linha, o tipo de erro/aviso e uma descrição do que estava errado;
  • Os dados do POST que foram usados ​​para fazer o request ao web service REST do Closure Compiler.

Em relação às otimizações, você pode selecionar entre as opções Simple e Advanced (não consideraremos o Whitespace only, pois não faz muita coisa).

O Simple transpilará e minificará seu código JS, além de alertar sobre a sintaxe e os erros mais perigosos (ainda que óbvios) que normalmente cometemos.

O avançado, por outro lado, é muito mais agressivo quando se trata de remover código, reorganizando toda a estrutura da sua implementação original.

Veja a imagem anterior do exemplo padrão “hello, world” na página do web service do Closure: ele reduziu o código e o simplificou, mas perdeu a função hello(), o que significa que referências externas a ele seriam interrompidas. Mas não se preocupe, exploraremos como corrigir isso.


Vamos dar outro exemplo um pouco mais complexo extraído dos tutoriais oficiais do Google:

// Copyright 2009 Google Inc. All Rights Reserved.

/**
 * Creates the DOM structure for the note and adds it to the document.
 */
function makeNoteDom(noteTitle, noteContent, noteContainer) {
  // Create DOM structure to represent the note.
  var headerElement = document.createElement('div');
  var headerText = document.createTextNode(noteTitle);
  headerElement.appendChild(headerText);
  
  var contentElement = document.createElement('div');
  var contentText = document.createTextNode(noteContent);
  contentElement.appendChild(contentText);

  var newNote = document.createElement('div');
  newNote.appendChild(headerElement);
  newNote.appendChild(contentElement);

  // Add the note's DOM structure to the document.
  noteContainer.appendChild(newNote);
}

/**
 * Iterates over a list of note data objects and creates a DOM
 */
function makeNotes(data, noteContainer) {
  for (var i = 0; i < data.length; i++) {
    makeNoteDom(data[i].title, data[i].content, noteContainer);
  }
}

function main() {
  var noteData = [
      {title: 'Note 1', content: 'Content of Note 1'},
      {title: 'Note 2', content: 'Content of Note 2'}];
  var noteListElement = document.getElementById('notes');
  makeNotes(noteData, noteListElement);
}

main();

Aqui, basicamente criamos uma estrutura de dados de notas, cada uma com atributos string de título e conteúdo. O restante é composto por funções utilitárias para iterar a lista de notas e colocá-las todas no documento por meio de cada função de criação. O mesmo código ficará assim depois de compilado pelo Closure Compiler:

for (var a = [{title:"Note 1", content:"Content of Note 1"}, {title:"Note 2", content:"Content of Note 2"}], b = document.getElementById("notes"), c = 0; c < a.length; c++) { var d = a[c].content, e = b, f = document.createElement("div"); f.appendChild(document.createTextNode(a[c].title)); var g = document.createElement("div"); g.appendChild(document.createTextNode(d)); var h = document.createElement("div"); h.appendChild(f); h.appendChild(g); e.appendChild(h); } ;

Observe que toda a lista de variáveis noteData foi alterada para uma declaração de objeto inline, que vem dentro do loop. As variáveis foram renomeadas para caracteres aleatórios do alfabeto. E você não pode reutilizar as funções anteriores em outros lugares; O Closure Compiler provavelmente teria colado a lista duas vezes se estivesse sendo chamado de outro lugar.

No entanto, a legibilidade e a compreensão do código não são boas (o que, é claro, não poderia ser usado em um ambiente de desenvolvimento).

Variáveis não utilizadas

Existem muitos cenários nos quais o Closure Compiler pode atuar — ou seja, problemas comuns ao nosso dia a dia como desenvolvedores JavaScript. Vejamos esse exemplo:

'use strict';
const helperModule = require('./helper.js');
var notUsed;

O que aconteceria com o código de saída gerado quando usamos o modo 'use strict'? Ou uma variável não utilizada, mesmo que você defina um valor mais tarde?

É comum criarmos muitas estruturas (não apenas variáveis, mas constantes, funções, classes, etc.) a serem removidas posteriormente que são facilmente esquecíveis — ainda mais se você estiver lidando com uma enorme quantidade de arquivos de código-fonte. Dependendo da complexidade de seus modelos ou de como você expõe seus objetos ao mundo externo, isso pode levar a situações indesejadas.

Bem, esse é o resultado:

var a = require(“./helper.js”);

As estruturas que não foram utilizadas foram identificadas e removidas automaticamente pelo Closure Compiler. Além disso, variáveis locais (let) e constantes (const) são substituídas por declarações var.

Fluxos condicionais

E quanto a um cenário em que um fluxo depende de outro fluxo condicional? Digamos que você tenha uma função, check(), que depende de outra, getRandomInt(), para gerar um número aleatório entre 0 e 1, e retornará true se for 1.

Com base nesse fluxo, não sabemos o que vai acontecer porque a função é aleatória — ou seja, somente em tempo de execução veremos se o código entra no if ou não:

let abc = 1;
if (check()) {
   abc = "abc";
}
console.info(`abc length: ` + abc.length);
function check() { return getRandomInt(2) == 1; }

function getRandomInt(max) {
   return Math.floor(Math.random() * Math.floor(max));
}

Eis o código compilado:

var b = 1;
1 == Math.floor(2 * Math.random()) && (b = "abc");
console.info("abc length: " + b.length);

O fluxo condicional foi analisado e reprogramado para uma única linha. Observe como o Closure Compiler verifica a primeira condição precedida por um operador &&. Este operador diz que somente se a primeira condição for verdadeira a segunda será executada. Caso contrário, se nosso número aleatório não for igual a 1, b nunca receberá "abc" como valor.

Que tal um if multi-condicional?

if(document == null || document == undefined || document == ‘’)
   console.info('Is not valid');

Eis o resultado:

null != document && void 0 != document && “” != document || console.info(“Is not valid”);

Os ifs condicionais foram aninhados. Esse é o comportamento padrão do Closure Compiler: ele sempre tenta diminuir o máximo possível, mantendo o código pequeno e ainda assim executável.

Referências externas e anotações

Pode ser potencialmente perigoso e improdutivo sempre revisar o código que foi compilado. Além disso, o que acontece se você desejar manter a função check() disponível globalmente para outros arquivos JavaScript? Existem alguns truques aqui, como a sugestão do Google de anexar a função ao objeto global window:

window.check = c;

Teríamos, portanto, o seguinte output:

var a = require("./helper.js"), b = 1;
c() && (b = "abc");
console.info("abc length: " + b.length);
null != document && void 0 != document && "" != document || console.info("Is not valid");
function c() {
return 1 == Math.floor(2 * Math.random());
}
window.check = c;

O sistema de tipos que o Closure Compiler checa é o coração de tudo. Você pode anotar seu código para ser mais "tipificado", o que significa que o Compiler verificará usos incorretos com base em suas anotações.

Vamos dar o exemplo da função getRandomInt(). Você precisa que seu parâmetro seja um número em todos os casos; logo, pode solicitar ao Closure Compiler para verificar se um caller está passando algo diferente:

function getRandomInt(/** number */ max) {
return Math.floor(Math.random() * Math.floor(max));
}
window['getRandomInt'] = getRandomInt;
getRandomInt('a');

Isso retornaria:


Mesmo que o arquivo seja sempre compilado para "warnings", você pode ter uma ideia do que está acontecendo com seu código, principalmente para códigos atualizados por muitas pessoas.

Outro recurso interessante são as definições de exportação. Se você decidir que não deseja que algo seja renomeado por meio da opção Advanced, também pode anotar seu código com:

/** @export */

Dessa forma, o código de saída não sofrerá mudanças.

Conclusão

Existem muitos cenários diferentes que você pode usar para testar o poder dessa ferramenta. Vá em frente, pegue seus próprios códigos JavaScript e experimente-os com o Closure Compiler. Faça anotações sobre o que ele gera e as diferenças entre cada opção de otimização. Você também pode pegar qualquer arquivo JavaScript externo e importá-lo no web service para fins de teste.

Lembre-se, também é possível executá-lo a partir da linha de comando ou mesmo do seu sistema de build, como Gulp ou Webpack. O Google também fornece uma pasta com exemplos no repositório oficial do GitHub, onde você pode testar mais recursos. Ótimos estudos!
Esse post foi traduzido do meu post no blog da LogRocket: Using Google Closure Compiler to deliver better JavaScript.

Comentários

Postagens mais visitadas deste blog

Integrando Android e PayPal com Java e MySQL - Parte 1

Uma das funcionalidades mais importantes da maioria dos apps mobile de hoje em dia é a possibilidade de se integrar com plataformas de pagamento online, tais como PagSeguro, MercadoPago ou a mais famosa de todas (a nível internacional): PayPal . Na maioria dos apps de eCommerce, que se caracterizam principalmente por compras e vendas online, não basta somente ter uma boa interface e lógica de negócio implementadas, é também preciso gerenciar tudo isso de forma segura, e para isso precisamos fazer uso de Web Services, bancos de dados, aplicações e outros tipos de recursos e operações no lado do servidor. Com o objetivo de cobrir uma implementação pouco vista em português, esse artigo, dividido em duas partes, visa ensinar como construir uma aplicação básica em Android , usando a biblioteca SDK do PayPal , uma integração server side com um projeto em Java Web , que fará uso de requisições HTTP via Web Services Restful (implementação Jersey ) e salvará os dados em um schema MySQ

Como acessar um iframe e seus elementos via jQuery?

Recentemente tive  um problema no projeto pois sentiu a necessidade de acessar um valor de um input que estava dentro de um iframe. Esse tipo de situação não é tão comum, uma vez que geralmente acessamos os valores do iframe para fora. Para acessar, de dentro de um iframe, um valor externo, utilizamos o seguinte código: $('#idDoElementoExterno', parent.document).val(); Entretanto, nunca tínhamos passado pela situação contrária. Pesquisando um pouco descobrimos uma alternativa, porém em JavaScript. Para ficar melhor o entendimento, vamos simular uma situação aqui. Temos uma página html "A.html" e dentro da mesma existe um iframe que aponta (src) para uma página "B.html": <!-- A.html --> <html> <head> <title>Testando iframe - jQuery</title> <script language="JavaScript"> function exibeValor() { // alert aqui! } </script> </head> <body> <input typ

"Content is not allowed in prolog" - Entendendo exceção no Seam

Recentemente tive um problema de edição em um arquivo .xhtml utilizando JBoss Seam, Richfaces e afins. A princípio a mensagem de erro não dizia muito a respeito da causa do mesmo: com.sun.facelets.FaceletException: Error Parsing /consulta.xhtml: Error Traced[line: 1] Content is not allowed in prolog. "O conteúdo não é permitido no prólogo". Mas que conteúdo? Em qual prolog? Depois de dar uma pesquisada descobri que o erro acontece em vista de terem sido colocados alguns caraceteres inválidos antes da declaração de documento xml na página xhtml. Em outras palavras, a primeira coisa que deve constar em um documento xml (afins) deve ser: <?xml version="1.0" encoding="utf-8"?> Qualquer coisa antes disso, até mesmo um simples espaço em branco, pode gerar o erro em questão. Por fim, lembre-se de que a declaração de documento xml segue o padrão de encoding definido. Logo temos: <!-- Inc