Modularizando JavaScript com Spring MVC e Jawr

javascript-logoEste post inicia uma série na qual vou expor técnicas que visam aumentar a manutenibilidade de código Javascript. Convenhamos: é uma linguagem de programação legal, porém muitas vezes o hype que a envolve nos impede de ver seus aspectos negativos. Neste post tratarei de uma das cicatrizes da linguagem que é a dificuldade que enfrentamos na modularização do código.

O problema: modularizar JavaScript usando Spring MVC

Se você tem a sorte de trabalhar com Grails tem à sua disposição uma solução maravilhosa que é o plugin resources que nos permite definir “módulos Javascript”. Infelizmente do lado Spring da cerca não temos algo tão popular. O que é um módulo Javascript?

Uma unidade de código que funciona em conjunto.

Muitas vezes esta unidade não é apenas um arquivo, mas vários, e é neste ponto que o problema começa a se manifestar. Imagine um módulo composto por dois arquivos: biblioteca.jsbiblioteca2.js. Sua equipe pode ter achado interessante dividi-lo em dois arquivos para paralelizar o esforço e com isto evitar todos aqueles incômodos relacionados ao merge de arquivos. Nesta situação, toda vez que você precisa usar este módulo, irá inserir em suas páginas código similar ao exposto a seguir:


<script type="text/javascript" src="/resources/js/biblioteca.js"></script>
<script type="text/javascript" src="/resources/js/biblioteca2.js"></script>

Ou então você poderia ter algum script que unisse os dois arquivos. O problema é que seria necessário modificar o seu processo de build e também fornecer garantias de que o arquivo gerado fosse sempre o mais atualizado possível (não é uma solução tãão bacana assim).

Agora, voltemos ao nosso exemplo. Imagine que este nosso módulo dependa do jQuery. Neste caso teríamos sempre de escrever o seguinte código em nossas páginas:


<!-- jQuery sempre irá ser carregado primeiro -->
<script type="text/javascript" src="/resources/js/jQuery.js"></script>
<script type="text/javascript" src="/resources/js/biblioteca.js"></script>
</span><script type="text/javascript" src="/resources/js/biblioteca2.js"></script>

Ainda pior: imagine que a ordem de carregamento dos arquivos biblioteca.jsbiblioteca2.js seja importante. Agora sua equipe precisa ser informada a respeito. Se for uma aplicação de uma única página, não será lá um grande problema, mas imagine uma aplicação maior, com mais de um template, etc. A coisa começa a se complicar.

A imagem a seguir expõe bem a situação do nosso projeto. Temos uma biblioteca composta por dois arquivos que depende de uma terceira:

jawr_dependencias

Não seria bacana se houvesse uma ferramenta que nos permitisse resolver os problemas a seguir de uma forma simples?

  • Documentar as dependências entre os nossos arquivos Javascript de tal forma que seja fácil para a equipe entender a organização interna do projeto.
  • Garantir que os arquivos sejam sempre carregados na ordem correta.
  • De alguma maneira otimizar o modo como estes arquivos são enviados ao browser: compressão via gzip, talvez até mesmo um merge dos arquivos.

Jawr entra em cena

logoJawr

Jawr é uma ferramenta que tem como objetivo resolver os três problemas que expus acima e mais alguns. Neste post irei tratar de uma parte bem pequena deste projeto: aquela que lida com Javascript. A grosso modo Jawr é um servlet que iremos inserir em nosso projeto fictício. Este servlet organizará nossos módulos Javascript em bundles.

O que é um bundle? É um módulo, tal como expus acima. Mais do que isto, um bundle alguns atributos fundamentais do nosso módulo:

  • Que arquivos compõem o nosso módulo
  • Em que ordem estes arquivos devem ser carregados
  • Quais as dependências do nosso bundle, isto é, que código este precisa para funcionar?

Voltando ao nosso projeto percebe-se que há dois bundles: biblioteca e jQuery, que podemos representar gráficamente tal como na imagem a seguir:

Nossos bundles

Nossos bundles

Poderíamos pensar em um bundle único composto pelos nossos arquivos da biblioteca e o do jQuery. O problema é que sabemos que jQuery será usado por diversos outros pontos da nossa aplicação: sendo assim é interessante que modularizemos esta parte a fim de que seja reaproveitada em outras partes do projeto.

Incluindo Jawr em nosso projeto

Nosso projeto é baseado em Maven. Sendo assim, para incluirmos o Jawr basta adicionar a dependência no arquivo pom.xml tal como no exemplo a seguir:

<dependency>
    <groupId>net.jawr</groupId>
    <artifactId>jawr</artifactId>
    <version>3.3.3</version>
</dependency>

Como disse logo acima, Jawr é essencialmente um servlet. Sendo assim temos também de alterar o arquivo web.xml para que fique similar ao exposto a seguir:

<!-- Servlet para lidar com Javascript -->
<servlet>
   <servlet-name>JavascriptServlet</servlet-name>
   <servlet-class>net.jawr.web.servlet.JawrServlet</servlet-class>

		<!-- Aonde se encontra o arquivo de configuracao -->
   <init-param>
       <param-name>configLocation</param-name>
       <param-value>/jawrJavascript.properties</param-value>
   </init-param>
   <init-param>
       <param-name>mapping</param-name>
       <param-value>/bundle/js/</param-value>
   </init-param>
   <load-on-startup>1</load-on-startup>
</servlet>
<!-- O mapeamento de URL -->	
<servlet-mapping>
   <servlet-name>JavascriptServlet</servlet-name>
   <url-pattern>/bundle/js/*</url-pattern>
</servlet-mapping>

Preste muita atenção no mapeamento de URL. Na documentação oficial do Jawr este é configurado para que lide com todos os recursos que terminem com a extensão .js. O problema com esta abordagem é que muitas vezes não queremos que todos os nossos arquivos Javascript sejam carregados como bundles. Talvez você queira ir adotando aos poucos a ferramenta em seu projeto (esta é a principal razão que vejo). Em nosso caso, Jawr só irá lidar com aqueles recursos Javascript que se encontrem na url /bundle/js/*. Nota importante: repare no parametro de inicialização mapping. Se for usar uma configuração como esta, este obrigatóriamente deve estar definido.

Configurando os bundles

No mapeamento do servlet foi incluído um parâmetro chamado configLocation. Em nosso projeto baseado em Maven este arquivo se encontra em /src/main/resources/jawrJavascript.properties. É neste arquivo que iremos definir todos os nossos bundles. Abaixo está um exemplo deste arquivo:

# Altere aqui caso esteja no seu ambiente de desenvolvimento
jawr.debug.on=false

# Devemos enviar os bundles compactados?
jawr.gzip.on=true

# De quantos em quantos segundos o servlet deve verificar se as configuracoes foram
# alteradas. Extreammente util em tempo de desenvolvimento!
jawr.config.reload.interval=30

# Primeiro bundle de exemplo: jQuery
# O identificador do bundle sempre é o que aparece antes do ‘.id’
# neste caso, é jquery
jawr.js.bundle.jquery.id=/bundle/js/jQuery.js
jawr.js.bundle.jquery.mappings=/resources/js/jquery-2.1.0.min.js

# Incluindo uma dependencia
jawr.js.bundle.biblioteca.id=/bundle/js/biblioteca.js
jawr.js.bundle.biblioteca.mappings=/resources/js/biblioteca.js, /resources/js/biblioteca2.js
jawr.js.bundle.biblioteca.dependencies=jquery

Um bundle Javascript é definido no arquivo com o prefixo jawr.js.bundle.[identificador do bundle]. A primeira linha, que termina com .id define como será a URL que define o nosso bundle. O bundle jQuery, por exemplo, sempre será carregado quando a URL /bundle/js/jQuery.js for acionada, enquanto o bundle biblioteca, pela URL /bundle/js/biblioteca.js.

Voltemos nossa atenção para o bundle biblioteca. A segunda linha da sua definição (mappings), define quais os arquivos que o compõe e a ordem na qual devem ser carregados. A terceira linha define quais as suas dependências. Neste caso, apenas uma: o bundle jquery.

Tendo feito isto temos todos os bundles em nosso projeto prontos para serem usados. O próximo passo é trabalhar no arquivo JSP.

Alterando o arquivo JSP

O projeto Jawr vem com uma biblioteca de tags bastante útil. Abaixo podemos ver um exemplo de sua aplicação:

<%– A declaração da biblioteca de tags –%>
<%@ taglib uri="http://jawr.net/tags" prefix="jwr" %>

<%– Carregando o bundle biblioteca –%>
<jwr:script src="/bundle/js/biblioteca.js"/>

E o que ocorre quando carrego a página? Obtenho o resultado a seguir:

<script type="text/javascript" src="/itexto/bundle/js/gzip_N1063700552/bundle/js/jQuery.js" ></script>
<script type="text/javascript" src="/itexto/bundle/js/gzip_N745101183/bundle/js/biblioteca.js" ></script>

Primeiro será inserida a dependência do bundle biblioteca e, em seguida, o código fonte do bundle biblioteca. Repare nas URLs: o Jawr irá automáticamente cachear os recursos Javascript. E sabem o que é mais legal? Ele também envia o Javascript comprimido (se o modo de depuração estiver desativado (veja as notas no arquivo que expus acima)). E não é só o conteúdo Javascript comprimido. Os dois arquivos que compõem o módulo biblioteca também são mesclados em um só, diminuindo assim o número de requisições necessárias. Simples assim. :)

Concluindo

Nese post apresentei uma pequena parte do projeto Jawr. Há mais neste servlet, que também pode ser usado para gerenciar recursos CSS e imagens. É importante salienar que a documentação do projeto é excelente: bem escrita e fácil de ser consultada.

Claro que a questão da modularização não termina aqui. Esta é apenas uma das soluções possíveis. Recentemente topei com um ebook online (e gratuito) chamado Writing Modular JavaScript with AMD, CommonJS & ES Harmony que trata da questão de uma forma muito mais profunda. Entra aí também o modo como escrevemos JavaScript (que não tratei neste post) e alguns outros pontos sobre os quais pretendo escrever mais a respeito em um futuro próximo.

O código fonte deste post pode ser acessado no meu GitHub: https://github.com/loboweissmann/jawr-testes

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.