Introdução ao Grunt e CoffeeScript

Este artigo tem como objetivo introduzir duas das tecnologias mais debatidas pela comunidade de desenvolvimento front-end do momento: a ferramenta de gerência de tarefas automatizadas para JavaScript, Grunt; e o framework de geração de código JavaScript, CoffeeScript.

Através da criação de uma aplicação que controla uma lista simples de alunos, exploraremos os principais recursos de ambas as tecnologias, não só individualmente, mas também em conjunto com si mesmas, e com outras ferramentas tão importantes quanto, tais como jQuery, Bootstrap, Sass e Compass.

Veremos também como gerar builds dinâmicas, criar threads de execução em hot deploy, e entender conceitos importantes de orientação a objetos e estrutura de dados atrelados a esse universo.

Quando falamos de desenvolvimento de software em geral, requisitos como web design ou deixar a execução de certas atividades ser feita no navegador do cliente para tornar a aplicação mais performática, fazem com que as ferramentas e linguagens que trabalham no front-end sejam mais cobradas em tal atendimento.

Para completar, não é difícil ver equipes que tenham termos como produtividade, código organizado e limpo, desenvolvimento ágil e múltiplas integrações e versionamentos, presentes na lista de obrigações de um projeto de software.

Várias tecnologias contribuíram e contribuem para isso, desde os pré-processadores de CSS, como o Sass e o Less, que ajudaram na dinamização do CSS antes feita de forma totalmente estática; até a criação de bibliotecas de compressão/minimização ou de testes unitários.

Mas um assunto que ainda traz demasiadas repercussões nesse universo novo, principalmente por se tratar de algo quase que completamente atrelado ao universo server side, é a automação de tarefas.

No mundo JavaScript, uma das bibliotecas de automação de tarefas mais usadas é o Grunt. Ele nasceu com o objetivo de aplicar na prática os conceitos de produtividade no desenvolvimento e integração de atividades junto ao JavaScript e às bibliotecas e frameworks que o seu projeto fizer uso.

Muitas empresas como Twitter, jQuery e Adobe fazem uso do Grunt em seus projetos padrão, ao passo que o mesmo consegue executar em conjunto com diversas tecnologias, tais como o Sass e Less (citados anteriormente), handlebars e o CoffeeScript.

O CoffeeScript, por sua vez, introduz um conceito parecido com o dos processadores de CSS, porém com “pré-compilação” de JavaScript. Através dele podemos, em uma linguagem abstraída e mais simplificada, compilar e gerar JavaScript.

O conceito de sintaxe é muito próximo ao de linguagens como o Ruby, o que acabou atraindo a atenção de muita gente desde que foi lançada sua primeira versão.

Neste artigo, iremos trabalhar essencialmente com estas duas tecnologias e tentaremos abordar o núcleo e os principais conceitos tanto do Grunt como do CoffeeScript, através da criação de uma aplicação simples e prática para gerenciamento de alunos, tal como demonstrado na Figura 1.

A mesma aplicação vai dispor de algumas listagens, com foco em entender como o CoffeeScript funciona para esse tipo de modelo programático, visualizando suas integrações junto ao Grunt. Mas antes, vamos entender melhor como funcionam ambas as tecnologias.

Tela do aplicativo de gerenciamento de alunos
Figura 1. Tela do aplicativo de gerenciamento de alunos.

Grunt

Em uma definição curta e simples, o Grunt é uma ferramenta de linha de comando baseada em tarefas para projetos em JavaScript.

De uma forma mais detalhada, podemos dizer que quando trabalhamos com um projeto em JavaScript, existe uma série de coisas que devem ser feitas regularmente, tais como concatenar arquivos, rodar alguma ferramenta de detecção de erros e problemas no código (como o JSHint, por exemplo), executar testes (unitários, de integração) ou modificar seus scripts.

Se você estiver copiando e colando o seu código JavaScript no site do JSHint (vide seção Links), provavelmente perceberá que há uma maneira melhor de fazer isso com o Grunt. Mesmo se estiver usando o objeto “cat” para concatenar arquivos ou uma linha de comando para comprimir os mesmos, seria bom ter um único conjunto unificado de comandos para todas aquelas tarefas extras, que você teve de fazer para cada projeto JavaScript que trabalhou, certo?

E é exatamente isso a que o Grunt se propõe. Com ele nós podemos simplesmente nos ater às regras de negócio e ao código em si das aplicações e esquecer as tarefas repetidas de automatização, geração ou gerenciamento.

Todavia, mesmo analisando todos os conceitos e exemplos que demos até aqui, muita gente ainda considera que o Grunt seja desnecessário e esteja nos projetos apenas como mais uma ferramenta que irá tomar o seu tempo. Vejamos algumas considerações, portanto:

Precisamos realmente das coisas que o Grunt faz?

Provavelmente sim. Mesmo se você já for um desenvolvedor experiente e conseguir lidar com tarefas simples como as que listamos de forma rápida, você provavelmente usa muitas ferramentas distintas para fazer cada coisa, e o Grunt pode ajudar a trazê-las para o mesmo contexto. Se você ainda não faz todas elas, provavelmente deveria e o Grunt pode ajudar. No final das contas, produtividade é a palavra-chave.

Mas o Grunt roda no Node.js e você não sabe Node...

Você não precisa saber Node.js para trabalhar com o Grunt, da mesma forma que não precisa saber C++ para usar o Word, ou PHP para usar o WordPress.

Temos outras formas de fazer o que o Grunt faz...

Provavelmente, mas a pergunta é: estão elas todas organizadas em um só lugar, configuradas para executar automaticamente quando for preciso, e compartilhadas para cada uma das pessoas que trabalhem no seu projeto?

O Grunt é uma ferramenta de linha de comandos, e você não sabe usar esse tipo de coisa.

Infelizmente esse é de fato um ponto negativo da ferramenta, pois não temos uma versão gráfica, com GUI. Mas como desenvolvedor é extremamente importante que você saiba lidar com interfaces de linha de comando.

Gruntfile.js

O Gruntfile.js constitui o arquivo de configuração do Grunt e será basicamente composto pelo formato descrito na Listagem 1.

Listagem 1. Formato padrão do arquivo de configuração Gruntfile.js.


// constantes e funções
module.exports = function (grunt) {
  grunt.initConfig({
     // configuração
  });
  // tarefas do usuário
}
    

É importante notar que o Grunt segue a especificação do CommonJS, um projeto desenvolvido para normatizar e padronizar convenções e estilos de JavaScript. Para isso, o Grunt exporta a si mesmo como um módulo que contém suas configurações e tarefas.

A fim de facilitar o processo de configuração, os usuários Grunt devem, idealmente, armazenar quaisquer portas, funções e outras constantes, que são utilizadas na parte superior do arquivo, como variáveis globais.

Isso garante que se uma função ou constante muda de repente, a edição da variável global no topo seria muito mais simples do que alterar seu valor em cada localização no arquivo.

Além disso, as constantes ajudam a fornecer informações através da atribuição de um nome de variável para cada valor desconhecido.

Por último, o Gruntfile vai encerrar com uma lista de tarefas definidas pelo utilizador. Por padrão, todos os plug-ins do Grunt têm uma tarefa respectiva que pode ser chamada para realizar a saída desejada. Tarefas definidas pelo usuário podem ser referenciadas no final do Gruntfile para serem executadas assincronamente.

Por exemplo, você pode querer definir uma tarefa para administradores de sistemas que vão enviar uma versão pronta para produção da sua aplicação web, combinando compressão, concatenação, e compilação de tarefas.

Você também pode definir uma tarefa “watch” para desenvolvedores web para auto compilar arquivos CoffeeScript ou Sass que exigem compilação para ser usados. Para uma equipe de estagiários, uma tarefa que combina plug-ins de validação para várias linguagens de codificação pode ser utilizada para evitar erros e auxiliar no seu processo de aprendizagem.

CoffeeScript

Basicamente, o CoffeeScript é uma pequena linguagem de programação que compila em JavaScript. Inspirado em linguagens como Ruby e Python, sua sintaxe consegue aumentar a legibilidade do código fonte, promovendo a brevidade no mesmo, e oferecendo novas funcionalidades, tal como a compreensão de listas. O código fonte do CoffeeScript é frequentemente muito mais curto do que o seu equivalente em JavaScript, e isso é possível ainda sem sacrificar o desempenho em tempo de execução.

Em dezembro de 2009, o desenvolvedor Jeremy Ashkenas anunciou o lançamento do CoffeeScript e seu compilador, que foi inicialmente escrito em Ruby e, posteriormente, reescrito em CoffeeScript. Dentre as vantagens do framework JavaScript, temos:

  • O CoffeeScript oferece uma sintaxe mais sucinta e coerente. A maioria dos desenvolvedores estima que o uso do CoffeeScript consegue diminuir a quantidade final de código em até um terço.
  • O CoffeeScript engloba as partes boas do JavaScript, como o ótimo modelo de objetos que subjaz o JavaScript, enquanto resolve algumas de suas partes ruins, como a eliminação da declaração with.
  • O código compilado do CoffeeScript muitas vezes executa tão rápido quanto código JavaScript, senão mais. Como Ashkenas relatou: "[Você] pode evitar declarações forEach lentas, e obter a velocidade nativa para loops com muitas operações."
  • O CoffeeScript oferece muitos recursos úteis, como "a correção de classes baseadas em protótipos, compreensões sobre matrizes e objetos, literais de função vinculados, variáveis lexicais seguras, desestruturação de atribuições", e muito mais.

Obviamente, o CoffeeScript tem algumas desvantagens, como o fato de termos ainda um outro compilador entre o desenvolvedor e o JavaScript antes de chegarmos ao resultado final. Desvantagem essa que tenta ser suprida com a geração de códigos JavaScript limpos e legíveis.

Além disso, as melhorias constantes na ferramenta fizeram com a mesma se destacasse e saísse da lista de críticas fortes que recebia no início quando não tinha um sistema definido de depuração de código (corrigido a partir da sua versão 1.6.1).

O compilador, por sua vez, pode ser dividido em duas categorias: core compiler (compilador núcleo), que executa em um ambiente JavaScript (como um browser, por exemplo), e o command-line compiler (compilador de linha de comando), que executa o JavaScript em tempo real no prompt de comandos.

Instalando o npm

O npm é, em poucas palavras, um gerenciador de pacotes para a plataforma Node.js, que, no nosso projeto, irá ajudar a simplificar a instalação, atualização e configuração dos pacotes de software que o mesmo precisar. Dessa forma, pode-se facilmente gerenciar as dependências de um projeto, sem quaisquer preocupações, de forma programável.

Como o Grunt é um projeto JavaScript construído usando o Node.js, naturalmente usaremos o npm para o controle de versão. O npm também será usado para instalar e atualizar os plug-ins do Grunt e o Bower (gerenciador de pacotes padrão que usaremos no projeto).

O registro npm também abriga uma variedade de outras ferramentas, incluindo o motor de testes Mocha, o motor de geração de templates Jade, e o framework web ExpressJS.

O leitor deve ter notado que estamos lidando com muitas ferramentas e integrações até agora, mas não se preocupe. A maioria delas não precisará ser entendida, uma vez que atuarão em segundo plano apenas para auxiliar como dependências.

Adicionalmente, a melhor razão para se usar o npm no nosso caso é que se precisarmos instalar um pacote que foi construído usando o Node.js, seria necessário baixar os arquivos e criar um pacote manualmente.

Usando o npm, pode-se instalar e atualizar pacotes de software com um simples comando. Ele também permite a instalação de projetos de uma versão específica, uma técnica que será utilizada ao longo do artigo para assegurar que as instruções do projeto alinham-se de forma consistente com o software.

Para instalar o npm, basta que instalemos o Node.js na máquina, uma vez que o mesmo já vem atrelado ao framework por padrão. Para tanto, acesso a página de downloads do Node.js (vide seção Links) e baixe o arquivo .msi de instalação para Windows, de acordo com a sua versão – 32 ou 64 bits (na página você também encontrará opções de instalação para os demais Sistemas Operacionais).

Execute o arquivo e siga todos os passos até o fim sem alterar as opções que já vierem marcadas por padrão. Após isso, é importante verificar se a instalação ocorreu com sucesso abrindo o seu prompt de comandos e executando os comandos a seguir:


        node –v
        npm -v
        

Isso irá imprimir as versões do Node e npm instalados, respectivamente. Caso apareça algo como “comando não encontrado”, refaça a instalação.

Para usar o npm, basta ter em mente alguns comandos importantes do mesmo:

  • npm install <pacote>: comando usado para instalar um pacote no diretório atual.
  • npm install –g <pacote>: faz a mesma coisa do comando anterior, porém definindo um escopo global para o mesmo pacote na máquina.
  • npm ls: serve para listar todos os pacotes do Node instalados no diretório atual.
  • npm install grunt: comando que usaremos para instalar a instância do Grunt.
  • npm update: serve para atualizar todos os pacotes no seu diretório atual. Muito útil, pois funciona exatamente como no Maven, sem nos preocuparmos com gerenciamento de versões.

Instalando o Bower

O Bower é um gerenciador de pacotes para o desenvolvimento front-end web. Ele será usado para instalar bibliotecas do lado do cliente para o nosso projeto. O Bower é semelhante ao npm na medida em que ajuda a facilitar a instalação, atualização e configuração de pacotes.

A principal diferença está na forma como ambas as ferramentas foram implementadas. Enquanto o npm usa uma árvore de dependências aninhadas e pode lidar com a instalação de várias versões de um pacote, o Bower mantém a sua árvore de dependências plana, sem aninhamentos.

Isso indica que cada cliente que usa o Bower só precisa de uma versão por dependência instalada, diminuindo desse modo a necessidade de espaço extra para cada projeto.

Sem o Bower, seria o mesmo que tradicionalmente baixar as bibliotecas front-end dos sites oficiais e colocá-las nos projetos. Infelizmente, essas bibliotecas não incluem os metadados que são necessários para automatizar a atualização ou configuração das mesmas. Usando o Bower e seu cache interno, nós seremos capazes de facilmente atualizar, instalar ou remover pacotes por demanda.

Uma vez instalado o npm, nós podemos instalar o Bower simplesmente executando o seguinte comando:

npm install –g bower@1.3.8

A flag -g permite que você execute o Bower como um binário global. Contanto que você esteja conectado à Internet e tenha o espaço necessário para a instalação, não deverá lidar com quaisquer erros durante todo este processo.

O valor 1.3.8 após o @ refere-se à versão que estamos baixando e é a mais recente no momento de escrita deste artigo. Se o autor desejar usar alguma versão anterior, tome bastante cuidado pois isso pode ocasionar alguns problemas referentes ao download de pacotes e plug-ins importantes para o correto funcionamento dos projetos.

Após isso, o Bower provavelmente estará configurado corretamente na sua máquina. Para verificar se tudo ocorreu bem, basta rodar o comando a seguir no prompt cmd, e a versão do Bower será impressa como 1.3.8:

bower –v

Dentre os comandos básicos que precisaremos para usar o Bower, destacam-se:

  • bower install <pacote>: usado para instalar o pacote (dependência) referenciado. Adicionalmente, o usuário pode definir qual versão do pacote deseja instalar, bastando para isso adicionar o caractere # precedido da versão após o nome do pacote.
  • bower isntall jquery: instala e configura a instância do jQuery pro projeto.
  • bower uninstall <pacote>: remove o pacote em questão.

Instalando o Grunt

Não há casos de uso para os quais o Grunt deva ser instalado globalmente. Ao iniciar um novo projeto, você deve configurar o mesmo como um projeto de faceta Grunt, isto é, dizer ao seu SO que aquele projeto está configurado para executar com o Grunt. Para isso, basta executar o seguinte comando:

npm install grunt

Se tudo ocorrer bem, você verá o processo completo acontecer. Para verificar, digite o comando:

grunt –v

Se a mensagem “Grunt: command not found” aparecer, então o seu SO não reconheceu a instalação e os pacotes subsequentes. Se isso acontecer, digite o comando a seguir:

npm install –g grunt-cli

Aguarde o processamento e no final você verá algo parecido com o conteúdo da Listagem 2 no seu console. O log mostra apenas as versões e a forma como a ferramenta será sincronizada em suas atualizações futuras.

Listagem 2. Resultado da instalação do Grunt.


D:\>npm install -g grunt-cli
C:\Users\Julio\AppData\Roaming\npm\grunt -> C:\Users\Julio\AppData\R
oaming\npm\node_modules\grunt-cli\bin\grunt
grunt-cli@0.1.13 C:\Users\Julio\AppData\Roaming\npm\node_modules\grunt-cli
 
├── resolve@0.3.1
├── nopt@1.0.10 (abbrev@1.0.5)
└── findup-sync@0.1.3 (lodash@2.4.1, glob@3.2.11) 
    

Criando página de teste

Para verificar se todas as configurações foram feitas corretamente até aqui e analisar como todas estas extensões trabalham em conjunto, bem como ter uma noção melhor do que o Grunt gera no final, façamos uma criação rápida de um “Alô Mundo”. Para isso, efetue o download do pacote de fontes deste artigo no topo da página e verifique que temos quatro projetos:

  • O projeto “alo-mundo-grunt” serve como modelo inicial para ser usado neste exemplo que vamos desenvolver.
  • O projeto “alo-mundo-grunt-final” é o projeto funcional final.
  • O projeto “grunt-coffee-app-escolar” servirá como template para a aplicação escolar a ser desenvolvida.
  • E o projeto “grunt-coffee-app-escolar-final” é referente ao projeto funcional final do artigo.

Posicione o arquivo do projeto de Alô Mundo em um local de sua preferência e de fácil localização via cmd. Agora, vamos configurar a faceta desse projeto executando os dois comandos:

npm install
        bower install

Você verá várias linhas de log serem impressas até que o processo termine. Se algo de errado acontecer, o motivo será impresso também no console. Note que além de termos que configurar as ferramentas a nível de SO, também precisamos configurá-las individualmente para cada projeto.

Os comandos são os mesmos, mas o npm lidará de formas distintas para ambas as situações. Note também que após esse procedimento, novas pastas serão criadas automaticamente dentro do diretório raiz, tais como “node_modules” (pasta que conterá os plug-ins e extensões) e “bower_components” (guardará os arquivos de componentes do bower, tal como o jQuery, etc).

Em seguida, dentro da pasta src, crie um novo arquivo chamado “index.html” e adicione o conteúdo da Listagem 3 ao mesmo.

Listagem 3. Página inicial do Alô Mundo.


<!doctype html>
<!--[if lt IE 7]>   <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]>      <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]>      <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js">
<!--<![endif]-->
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>Alô Mundo - Grunt | CoffeeScript</title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width">
  </head>
  <body>
    <div id="container">
    </div>
    <!-- build:js scripts/compiled.js -->
    <script type="text/javascript" src="bower_components/jquery/jquery.js"></script>
    <script type="text/javascript" src="scripts/main.js"></script>
    <!-- endbuild -->
  </body>
</html>
    

Esta será a estrutura HTML básica que usaremos para o exemplo. Não há muito a acrescentar exceto pelo uso das tags de importação dos scritps que faremos uso neste exemplo.

Note que o jQuery se encontra entre eles, mas sequer baixaremos qualquer arquivo do mesmo, pois os gerenciadores serão responsáveis por lidar com tais dependências. É importante que o leitor se atenha a analisar bem a estrutura de tags <meta> do cabeçalho, pois quando efetuarmos a compressão dos arquivos, o resultado será bastante diferente.

Após isso, crie também um arquivo dentro da pasta src/scripts chamado “main.js” e adicione o seguinte conteúdo:


$(function() {
 $("#container").html("Alo Mundo");
});

Isso será o bastante para imprimir a mensagem que desejamos na tela. Para tal, execute o arquivo HTML em qualquer navegador e você verá a mensagem impressa. Perceba que sequer fizemos qualquer download de biblioteca para o projeto, o que atesta o funcionamento das extensões de automação.

Finalmente, para efetuar o “deploy” da aplicação, isto é, gerar os arquivos comprimidos que desejamos, vamos editar o arquivo Gruntfile.js que já veio na pasta de template.

Se o mesmo não existir você deverá criar um com a configuração modelo mínima vista anteriormente. Como o arquivo é deveras grande, vamos fazer as configurações aos poucos entendendo cada uma. Para criar a versão de deploy do projeto, nós precisaremos criar uma pasta (dist) e limpar sempre o conteúdo dela antes de atualizá-la.

Para tanto, é necessário criar uma tarefa que faça isso. Veja na ListagemListagem 4 o código necessário.

Listagem 4. Tarefa para limpar a pasta de deploy.


config["clean"] = {
       build: {
             files: [{
             dot: true,
             src: [
                    "dist/*",
                    "!dist/.git*"
             ]
             }]
       }
};
    

Adicione todos os conteúdos de tarefas logo após a criação do objeto config. Esse código realiza a configuração necessária para o plug-in “grunt-contrib-clean”. Repare que a configuração segue o modelo de JSON básico, com objetos e listas sendo configuradas com chaves e colchetes. O atributo files distribui todos os arquivos ou padrões de diretórios de acordo com o vetor de strings configurado na opção src.

A opção dot, por sua vez, é responsável por definir que todos os arquivos devem ser excluídos, inclusive os arquivos escondidos. Por fim, o valor “!dist/.git” serve para configurar que nenhum arquivo com essa extensão deve ser adicionado ao pacote final e pode ser usada para quaisquer extensões, como SVN, CVS, etc.

Logo após, nós devemos limpar o arquivo HTML. Para fazer isso, adicione o objeto de configuração definido na Listagem 5 depois da declaração do objeto anterior.

Listagem 5. Código de configuração da limpeza do HTML


config["htmlmin"] = {
       build: {
             options: {
                    collapseBooleanAttributes: true,
                    removeAttributeQuotes: true,
                    removeRedundantAttributes: true,
                    removeEmptyAttributes: true
             },
             files: [{
                    expand: true,
                    cwd: "src",
                    src: "{,*/}*.html",
                    dest: "dist"
             }]
       }
};
    

Note que a opção collapseBooleanAttributes vai ajudar na remoção das atribuições desnecessárias dos atributos para os tipos booleanos, como os tipos “readonly” e “disabled”. Em outras palavras, ela transforma a tag <input readonly="readonly"> em <input readonly>.

A opção removeAttributeQuotes irá remover as aspas duplas das atribuições sempre que possível. Isso vai transformar a tag <div id="container"> </div> em <div id=container> </ div>, por exemplo.

Ao longo dos anos, os navegadores tornaram-se melhor em heuristicamente determinar o conteúdo entre tags. Como tal, certos atributos podem agora ser considerados redundantes. Ao habilitar a opção removeRedundantAttributes, você poderá automaticamente remover atributos como type="text/javascript" de vez das suas páginas HTML.

Por último, habilite a opção removeEmptyAttributes para remover valores atribuídos com strings vazias.

Paralelo a isso, nós precisaremos configurar também a forma como os arquivos JavaScript serão comprimidos. Adicione o conteúdo da Listagem 6 na sequência do arquivo de configuração.

Listagem 6. Código de configuração para compressão e plug-in uglify


config["useminPrepare"] = {
       options: {
             dest: "dist"
       },
       html: "src/index.html"
};
config["usemin"] = {
       options: {
       dirs: ["dist"]
       },
       html: ["dist/{,*/}*.html"]
};
 
config["uglify"] = {
       options: {
             mangle: false
       }
};
    

O plug-in grunt-usemin será usado para comprimir nossos arquivos JavaScript. O plug-in usemin, por sua vez, irá unificar os demais plug-ins grunt-contrib-concat, grunt-contrib-uglify e grunt-ver.

Todos estes plug-ins lidam essencialmente com a compressão tanto no lado dos scripts quanto do HTML em si, ambos andam em conjunto. Observe que os destinos sempre apontam para a pasta dist que será usada para essa finalidade.

A opção mangle serve para desligar a ofuscação e pode causar alguns erros de percalço, então tome cuidado se tiver lidando com muitas exceções no console de log do seu navegador.

Para finalizar, basta que criemos mais uma tarefa a fim de evitar o famoso problema dos navegadores modernos que armazenam localmente em cache arquivos usando URLs para diminuir o tempo de carregamento.

Isso indica que o JavaScript, o CSS, e até mesmo arquivos de imagem podem ser carregados localmente em vez de a partir do servidor web oficial. Para resolver isso, é aconselhável armazenar em cache seus arquivos, adicionando uma HashKey do arquivo antes do nome do mesmo.

Desta forma, toda vez que um arquivo é atualizado, seu hash o será também, atualizando consequentemente o nome do arquivo. Para conseguir isso com os arquivos JavaScript em nosso projeto, vamos adicionar o bloco de código da Listagem 7 ao Gruntfile.js.

Listagem 7. Objeto de configuração da HashKey e variável vetor de tarefas.


config["rev"] = {
       files: {
             src: [
                    "dist/scripts/{,*/}*.js",
             ]
       }
};
 
grunt.initConfig(config);
 
var tasks = [
       "clean",
       "useminPrepare",
       "htmlmin",
       "concat",
       "uglify",
       "rev",
       "usemin"
];
    

Repare também que adicionamos ao final do script, após a inicialização da configuração do Grunt, uma variável de vetor para armazenar os nomes de todos os objetos de tarefas criados. Estas mesmas tarefas irão executar de forma síncrona quando dermos o build no projeto, pela ordem que foram definidas. E para finalmente gerar a build, basta executarmos o comando a seguir no terminal, de dentro da pasta raiz do projeto:

grunt build

Após isso, você verá vários logs no console e quando finalizar, poderá encontrar uma nova pasta dist criada no projeto, com vários arquivos dentro, dentre os quais estarão as versões comprimidas do arquivo js, bem como do HTML.

Abra os mesmos arquivos com o editor de texto e analise a sua constituição. Note que o conteúdo gerado é completamente gerenciado via JavaScript, até mesmo o conteúdo do corpo HTML.

Essa é a ideia base destes frameworks. Você poderá executar o arquivo dist/index.html normalmente no seu browser e o resultado será o mesmo.

Criando o projeto escolar

Para dar início à construção da aplicação, verifique o arquivo “grunt-coffee-app-escola” que você baixou nos fontes do artigo e o posicione em um local de fácil navegação.

Após isso, façamos as mesmas configurações do projeto anterior, adicionando as dependências do npm e Bower ao mesmo. Adicionalmente, a biblioteca Bootstrap também será adicionada para que possamos usar seus recursos de componentes visuais.

Comecemos então, pela configuração das tarefas no arquivo Gruntfile . Após a variável config vamos adicionar o código da Listagem 8.

Listagem 8. Código que cria tarefa para “assistir” as alterações nos arquivos.


var config = {};
       config["watch"] = {
             options: {
             nospawn: true
       },
       coffee: {
             files: ["src/coffee/{,*/}*.coffee"],
             tasks: ["coffee:server"]
       },
       compass: {
             files: ["src/styles/{,*/}*.{scss,sass}"],
             tasks: ["compass:server"]
       }
};
    

Essa configuração serve explicitamente para informar ao Grunt quais arquivos devem ser recriados e atualizados ante as mudanças diretas nos arquivos do CoffeeScript que estarão localizados na pasta coffee, e do Sass, presentes no diretório css.

Essa configuração somente funcionará se você tiver as últimas versões do Ruby e Compass (seção Links) instaladas no seu computador e devidamente reconhecíveis pelo S.O., através das variáveis de ambiente. Após instalar o Ruby o comando gem automaticamente estará disponível. O Compass é uma gem do Ruby e pode ser instalada através do comando:

gem install bundler -r
        --source http://rubygems.org/

Você pode informar a versão que deseja configurar, mas é mais aconselhado usar sempre esta versão do comando, que irá lidar com as mais recentes. Demonstraremos o uso do watch mais a frente.

Continuando com a lista de tarefas, vejamos o código da Listagem 9.

Listagem 9. Código das tarefas do compass e coffee.


config["compass"] = {
       options: {
             sassDir: "src/styles/sass",
             cssDir: "src/styles",
             importPath: "src/bower_components",
             relativeAssets: false
       },
       dist: {},
       server: {}
};
 
config["coffee"] = {
       dist: {
             files: [{
                    expand: true,
                    cwd: "src/coffee",
                    src: "{,*/}*.coffee",
                    dest: "dist/scripts",
                    ext: ".js"
             }]
       },
       server: {
             files: [{
                    expand: true,
                    cwd: "src/coffee",
                    src: "{,*/}*.coffee",
                    dest: "src/scripts",
                    ext: ".js"
             }]
       }
};
    

Neste código podemos ver a criação de duas tarefas: compass e coffee. Ambas lidarão com a forma como o deploy dos arquivos de CSS e CoffeeScript será feito, respectivamente. Observe que na declaração da primeira, nós simplesmente criamos referências aos seus diretórios, onde serão encontrados os arquivos de CSS das dependências, do Sass, bem como a não aceitação de caminhos relativos para lidar com os imports.

Na segunda, temos a tarefa grunt-contrib-coffee sendo criada, com a distribuição dos arquivos de deploy e do servidor. As mesmas características de diretórios podem ser visualizadas através das opções src, dest e cwd.

O leitor também deverá se atentar ao fato de que estas são as pastas que precisará manipular quando for trabalhar com os arquivos. Se desejar alterar seus nomes, fique à vontade.

Finalmente, execute o comando para iniciar o “watch” no console:

grunt watch

Isso irá iniciar a thread grunt-contrib-watch que criamos antes e verificar sempre que você fizer alterações nos arquivos e salvá-los. Por enquanto, vamos deixar a mesma executando em segundo plano, depois voltamos pra ela.

Como primeira alteração de fato na implementação, vamos navegar até o arquivo src/styles/sass e incluir o seguinte trecho de código no mesmo:

@import "bootstrap-sass/lib/bootstrap.scss";

Esse trecho irá se responsabilizar por importar as dependências de CSS do Bootstrap para o arquivo de CSS do projeto. Note que após salvar o arquivo, um novo arquivo main.css será criado na pasta src/styles com todo o conteúdo de CSS que precisaremos para o exemplo. Esse é o resultado de criar uma thread para “assistir” às alterações como fizemos.

Em seguida, vamos começar as alterações nos arquivos do CoffeeScript, de fato. Abra o arquivo main.coffee na pasta src/coffee/app e adicione o conteúdo da Listagem 10 ao mesmo.

Listagem 10. Código do arquivo principal do CoffeeScript no projeto.


Aluno = window.GLOBALS.Aluno
Painel = window.GLOBALS.Painel
 
$ ->
       lista_alunos = [
             {
                    nome: "John"
                    serie: 4
                    notas: 40
                    inadimplente: true
             }
             {
                    nome: "Jane"
                    serie: 4
                    notas: 40
                    inadimplente: true
             }
             {
                    nome: "Max"
                    serie: 4
                    notas: 40
                    inadimplente: false
             }
       ]
       
       alunos = {}
       
       for modelo in lista_alunos
             alunos[modelo.nome] = new Aluno modelo.nome, modelo.serie, modelo.notas, modelo.inadimplente
             
       painel = new Painel $("#tabela-alunos"), alunos
    

Comecemos a análise deste código pela quantidade de recursos que o CoffeeScript aceita, que se assemelham à forma como trabalhamos com linguagens de programação comuns, principalmente se você tiver familiaridade com o Ruby. Logo no início do arquivo, vemos a declaração de dois objetos que serão usados no exemplo.

O CoffeeScript trabalha de forma semelhante às outras linguagens no que se refere à importação de classes, o conceito é o mesmo: devemos importar as classes antes de usá-las em outra classe.

O início da declaração do conteúdo do arquivo vem após o sinal ->, este que será usado também para determinar o início de outros blocos de código como métodos e construtores.

Em seguida, repare que criamos a variável lista_alunos que servirá como uma espécie de Collection para a criação dos objetos de Aluno e seus respectivos atributos e valores.

O vetor será usado para completar a lista de alunos criada em sequência através do uso do forEach (famoso no Java, e com o mesmo conceito), criando, finalmente, um objeto Painel e passando a lista de alunos como parâmetro.

Repare que a sintaxe do CoffeeScript se assemelha ao JSON quando da criação de objetos. Além disso, a criação de novos objetos implica na passagem de parâmetros e valores sem o uso de parênteses como em outras linguagens como o Java.

Vamos criar agora as classes de entidades que serão usadas para compor os atores do nosso aplicativo. Abra o arquivo “pessoa.coffee” no diretório src/coffee/entidades e o preencha com o código da Listagem 11.

Listagem 11. Classe Pessoa no projeto CoffeeScript.


class Pessoa
       constructor: (@nome) ->
             throw "Nome inválido" if @nome.length is 0
             
       getNome: ->
             @nome
                    
window.GLOBALS.Pessoa = Pessoa
    

Aqui nós podemos ver a representação fiel do início de uma herança no CoffeeScript. Esta classe simples será usada como classe pai para os demais tipos de pessoa que vierem a surgir no projeto.

A maior vantagem nessa abordagem, além da simplificação do código para o desenvolvedor, é a possibilidade de lidar com os prototypes do JavaScript de forma abstraída. Além disso, também estamos criando o nosso primeiro construtor com uma sintaxe deveras familiar.

Os atributos de classe devem ser referenciados com o uso do @ no início de cada nome e o lançamento de exceções pode ser feito através da palavra reservada throw. Repare que a condição para lançar a exceção é sempre feita após o lançamento da mesma com a respectiva mensagem.

Isso é um padrão na linguagem e você deve obedecer. Para delimitar o início e fim dos blocos, nós usamos a indentação em conjunto com as linhas em branco, ou seja, sempre que quiser criar um novo método ou bloco, dê o espaço de uma linha abaixo.

Dando sequência, criaremos agora então a classe filha de Pessoa, Aluno. Para isso, abra o arquivo aluno.coffee no mesmo diretório e acrescente o código da Listagem 12 ao mesmo.

Listagem 12. Código de criação da classe Aluno.


Pessoa = window.GLOBALS.Pessoa
 
class Aluno extends Pessoa
       constructor: (@nome, @serie, @notas, @inadimplente) ->
             super(@nome)
             throw "Série inválida" if @serie < 1
             throw "Nota inválida" if @notas < 0
       
       getSerie: ->
             @serie
             
       getNotas: ->
             @notas
             
       getInadimplente: ->
             @inadimplente
             
       isReprovado: ->
             @serie is 0
             
       aprovar: ->
             @serie++
             @notas += 60
             
       reprovar: ->
             @serie = 0
             @notas = 0
       
window.GLOBALS.Aluno = Aluno
    

A única novidade neste arquivo é o uso do super() logo no início do construtor. Assim como em outras linguagens OO, essa expressão é usada para efetuar uma chamada direta ao construtor da classe pai passando o valor do atributo nome como parâmetro.

Dentro do mesmo construtor ainda, temos duas validações que serão úteis para verificar se os valores da série e nota do aluno estão sendo passados corretamente. O restante do código se atém à criação dos métodos de get, bem como os métodos para aprovar e reprovar o aluno.

Por último, vamos criar agora a criação do conteúdo do arquivo painel.coffee (Listagem 13).

Listagem 13. Código do arquivo painel.coffee para exibição – parte 1.


Aluno = window.GLOBALS.Aluno
 
class Painel
       constructor: (@painel, @alunos) ->
             @init()
             
       init: ->
             _this = this
 
             @render()
 
             $("#btn-incluir").click ->
                    chave = $("#input-aluno").val()
                    _this.alunos[chave] = new Aluno chave, 1, 40, false
                    _this.render()
                    
       getAlunos: ->
             @alunos
             
       setAlunos: (alunos) ->
             @alunos = alunos
    

Por ser muito grande, vamos dividir o entendimento deste arquivo em duas partes. Na mesma listagem, é possível observar que, além do construtor do painel que recebe os parâmetros de identificador do corpo HTML e a lista de alunos, respectivamente; e dos métodos de get/set, temos o método init, que será responsável por inicializar os atributos e objetos para uso posterior na tela e que está sendo chamado diretamente pelo construtor.

Esse método basicamente configura o comportamento do clique no botão de inclusão de alunos que irá automaticamente verificar o valor do campo de texto, adicionando o item à lista e, consequentemente, à tabela HTML.

A segunda parte dessa classe é referente ao método render (Listagem 14) que irá recuperar o objeto DOM tbody da tabela na página HTML e percorrer a lista de alunos criadas no main.coffee inicialmente.

Na iteração, verificamos se o aluno é inadimplente para assim criar a checkbox correspondente, bem como montar as colunas da tabela com o conteúdo de cada um deles.

Por fim, o método irá também escutar os cliques nos botões de aprovação e reprovação da tabela, tratando de remover ou incrementar o conteúdo do aluno naquela linha.

Listagem 14. Código do arquivo painel.coffee para exibição – parte 2.


render: ->
             _this = this
             
             @painel.find("tbody").html ""
             
             for chave, valor of @alunos
                    if valor.isReprovado()
                           continue
                    
                    if valor.inadimplente
                           check = "<input type="checkbox" name="inadimplencia" checked="true" disabled="true"/>"
                    else 
                           check = "<input type="checkbox" name="inadimplencia" disabled="true"/>"
                    
                    linha =
                    "<tr data-id="#{chave}">
                           <td>#{chave}</td>
                           <td>#{valor.serie}</td>
                           <td>#{valor.notas}</td>
                           <td>#{check}</td>
                           <td>
                                  <button class="btn btn-primary btn-aprovar"
                                  data-id="#{chave}">Passar</button>
                                  <button class="btn btn-danger btn-reprovar" data-id="#{
                                  chave}">Reprovar</button>
                           </td>
                    </tr>"
                    @painel.find("tbody:last").append(linha)
                    
             @painel.find(".btn-aprovar").each ->
                    $(this).click ->
                           chave = $(this).attr("data-id")
                           _this.alunos[chave].aprovar()
                           _this.render()
                           
             @painel.find(".btn-reprovar").each ->
                    $(this).click ->
                           chave = $(this).attr("data-id")
                           _this.alunos[chave].reprovar()
                           _this.render()
                           
window.GLOBALS.Painel = Painel
    

Observe-se sempre em relação ao uso da identação. É muito importante fazê-la corretamente para que o framework entenda onde começa e termina cada instrução.

Ao final desse processo, verifique que a thread watch criou vários arquivos .js correspondentes ao conteúdo dos arquivos CoffeeScript no diretório src/scripts.

Antes de testar, não podemos esquecer de criar o arquivo HTML que conterá o conteúdo da tabela a ser exibida. Para isso, crie um novo arquivo “index.html” na raiz da pasta src e adicione o conteúdo da Listagem 15.

Listagem 15. Conteúdo do arquivo index.html do nosso projeto.


<!doctype html>
<!--[if lt IE 7]>   <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]>      <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]>      <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js">
<!--<![endif]-->
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>Sistema de Gerenciamento de Alunos</title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width">
    <link rel="stylesheet" href="styles/main.css">
  </head>
  <body>
    <header class="navbar navbar-static-top navbar-inverse" role="banner">
      <div class="container">
        <div class="navbar-header">
          <button class="navbar-toggle" type="button" data-toggle="collapse" data-target=".bs-navbar-collapse">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand">Sistema de Gerenciamento de Alunos</a>
        </div>
        <nav class="collapse navbar-collapse" role="navigation">
        </nav>
      </div>
    </header>
    <div class="container">
      <div class="row">
        <div class="col-lg-12">
          <table id="tabela-alunos" class="table table-hover table-striped table-border">
            <thead>
              <tr>
                <th>Nome</th>
                <th>Série</th>
                <th>Media Notas</th>
                <th>Inadimplente</th>
                <th>Ações</th>
              </tr>
            </thead>
            <tbody>
            </tbody>
          </table>
          <input id="input-aluno" class="form-control input-sm" style="width: 200px; 
          display: inline-block" /> <a id="btn-incluir" 
          class="btn btn-primary">Adicionar</a>
        </div>
      </div>
    </div>
    <!-- build:js scripts/compiled.js -->
    <script type="text/javascript" src="bower_components/jquery/jquery.js"></script>
    <script type="text/javascript" src="scripts/app/init.js"></script>
    <script type="text/javascript" src="scripts/entidades/pessoa.js"></script>
    <script type="text/javascript" src="scripts/entidades/aluno.js"></script>
    <script type="text/javascript" src="scripts/entidades/painel.js"></script>
    <script type="text/javascript" src="scripts/app/main.js"></script>
    <!-- endbuild -->
  </body>
</html>
    

Esse arquivo é muito semelhante ao primeiro que criamos no projeto de Alô Mundo, e difere apenas pela criação da tabela e a inclusão de todas as classes CSS nos elementos do corpo HTML.

É importante não faltar com essas classes, pois elas tratarão de aplicar o estilo na página tal como associado às regras do Bootstrap. Além disso, também estamos efetuando a criação do botão de adição de novos alunos à listagem seguindo os mesmos requisitos de código JavaScript que foram criados nos arquivos coffee.

Perceba também que os imports dos arquivos JavaScript no final precisam estar na ordem definida para evitar esquecimento de algum objeto ou até mesmo erro de carregamento no browser. É importante não remover os comentários antes e depois deles, pois serão usados para efetuar a build.

Agora você pode executar o arquivo HTML no navegador e visualizar a impressão da Figura 1 no mesmo. Você poderá testar e verificar o correto funcionamento de todos os botões e ações da página.

Caso alguma coisa não esteja funcionando corretamente, selecione a tecla F12 (no Chrome) para entrar no modo desenvolvedor. As mensagens de erro estarão disponíveis em vermelho e irão te ajudar a ver se você esqueceu alguma coisa.

Para finalizar a build, nós ainda precisamos efetuar algumas alterações no arquivo de Gruntfile . A primeira delas é incluir no mesmo arquivo as tarefas que criamos para o projeto Alô Mundo: clean, useminPrepare, usemin, htmlmin, uglify e rev, sem alterações.

Após isso, ainda precisamos de mais duas tarefas (Listagem 16): copy, usada para efetuar a transferência dos arquivos de uma pasta ao seu destino final; e cssmin, que se responsabilizará por comprimir os arquivos de CSS.

Listagem 16. Tarefas copy e cssmin no arquivo Gruntfile .


config["copy"] = {
       build: {
             files: [{
                    expand: true,
                    dot: true,
                    cwd: "src",
                    dest: "dist",
                    src: []
             }]
       }
};
 
config["cssmin"] = {
       dist: {
             files: {
                    "dist/styles/main.css": [
                           "src/styles/{,*/}*.css"
                    ]
             }
       }
};
    

Para finalizar, vamos adicionar a mesma lista de tarefas num vetor, porém com as novas agora adicionadas (Listagem 17).

Listagem 17. Vetor com a lista de tarefas atualizada.


var tasks = [
       "clean:build",
       "useminPrepare",
       "htmlmin",
       "cssmin",
       "concat",
       "uglify",
       "copy",
       "rev",
       "usemin"
];
    

Mais uma vez, para gerar o build basta executar o comando grunt build e tudo estará nos conformes para ir pra produção.

Se o leitor encontrar quaisquer problemas pelo caminho, ou alguma das ferramentas não funcionar, verifique os passos e refaça-os até que esteja tudo ok.

A vantagem de usar esse tipo de recurso é que só perdemos tempo configurando tudo uma vez, e as atualizações de versões podem ser feitas automaticamente com os respectivos comandos de update.

Não é preciso ser um expert para dominar os recursos do Grunt e do CoffeeScript, e apenas com o desenvolvimento de um sistema simples como esse já conseguimos ver muito do potencial de ambas as tecnologias. Mas ainda há muito mais a ser explorado, como polimorfismo, encapsulamento e outros conceitos de orientação a objetos, listas, estruturas dinâmicas, etc.


Links Úteis

Saiba mais sobre Front-end ;)

  • Já ouviu falar em Single Page Applications?:
    Você sabe o que são Single Page Applications (SPA)? Neste DevCast falaremos sobre esse modelo de aplicação que vem ganhando espaço no mercado e que deve ser conhecido pelos programadores web.
  • Formulário de cadastro com JSF e Bootstrap:
    Aprenda neste exemplo como criar interfaces ricas com Bootstrap e JSF. Saiba como o Pass-through elements pode te ajudar a ter mais controle sobre o HTML gerado pelos componentes nativos.