Rails, Bower e o Asset Pipeline

Uma das coisas que mais curto no Rails é a facilidade em criar coisas complexas de uma forma muito simples.

Em questão de minutos você consegue criar modelos, controllers, definir rotas e tudo mais. É tudo muito rápido, mesmo que você seja adepto aos testes (sejam eles unitários, de integração ou qualquer outro tipo).

É lógico que pra aplicações mais complexas as coisas não são tão banais quanto eu falei acima. Você precisa quebrar vários conceitos seus de organização e de estrutura de código pra migrar de uma linguagem qualquer para o Ruby, e consequentemente aprender mais dos macetes do Rails.

Apenas uma coisa me incomoda às vezes, e é sobre o cuidado com os assets que quero falar.

Sprockets

Primeiramente, dando uma pincelada rápida nisso, o Rails traz no seu pacote de gems o Sprockets. Se você nunca ouviu falar, dê uma checada nesse guia do Asset Pipeline e como ele funciona no Rails.

Com essas informações você já consegue ter uma ideia de como o Sprockets trabalha compilando, compactando e minificando seus assets.

Uma das coisas que você vai perceber ao compilar seus assets no Rails (pelo menos na versão 4.0.*) é que o Sprockets, por default, não compila uma versão sem o SHA, na pasta public/assets/. Ainda não sei confirmar se isso é apenas um comportamento da 4.0.* em diante ou se isso já era default antes, mas pelo que me lembro, na version 3.2.15 que utilizávamos aqui na MustacheLabs, ainda havia o "fallback" para servir assets sem o digest, com a rake task rake assets:precompile:nondigest.

O digest, explicando de modo simplório, nada mais é do que a versão do seu arquivo compilado com uma identidade, uma hash SHA, atrelada ao nome do arquivo.

Um main.css.scss vai gerar um main-85f7f93e0fad3097f6cc6d5996b61b73.css. Isso serve pra controlar o cache e pra checar se houve alguma alteração no arquivo, antes de compilar o mesmo novamente.

Em partes é bom, porque assim você tem mais certeza de que, o que vai ser servido é o asset realmente compilado. O ruim é quando lidamos com plugins, bibliotecas ou frameworks de terceiros, e esses, estão fora de uma gem ou do contexto da sua aplicação.

Onde o Bower entra

O Bower é um gerenciador de pacotes feito em Node. Pra utilizar no seu projeto, basta instalar o módulo via npm (pra isso você precisa ter instalados Node e npm).

npm install -g bower

Feito isso, você precisa criar a estrutura básica pra que o Bower funcione.

bower init

Ele é interativo, portanto basta responder aos comandos/perguntas no terminal e configurar da sua maneira.

Veja que, depois desse setup, um bower.json será criado na home do seu projeto.

As vantagens do uso do Bower são:

  • componentizar as bibliotecas, plugins ou frameworks que você usa no desenvolvimento de interface;
  • com os componentes "rastreáveis" é possível atualiza-los sem a necessidade de baixar o pacote novamente, tudo "na mão";
  • facilidade de entender as dependências de cada um dos componentes que você tem no seu projeto;
  • criar um package só da sua equipe - isso é válido pra quem usa o mesmo "esqueleto" de aplicação em todos os projetos.

Pra adicionar um componente, como por exemplo o Bootstrap, basta rodar o comando:

bower install bootstrap

Se você olhar o diretório do seu projeto vai ver que, dentro de bower_components, ele criou dois paths: bootstrap e jquery. Ah! essa pasta bower_components é configurável, portanto você pode inserir componentes onde quiser :)

O Bootstrap depende do jQuery pra que alguns componentes dele funcionem (modal, tooltip, transitions, etc.).

Ele é instalado porque no package do Bootstrap, existe um outro arquivo bower.json, que lista as dependências dele. O Bower identifica isso e já instala essas dependências pra você.

Se você já tinha o jQuery declarado como dependência do seu projeto, dentro do arquivo bower.json, ele mantém ou atualiza a version do jQuery, dependendo da versão que o Bootstrap necessite.

Mágico não? Pois é!

Aqui na Mustache utilizamos o bower-rails, uma gem que possui algumas rake tasks do Bower. Dê uma lida aqui pra entender melhor e veja os benefícios de utiliza-lo. Alguma coisa ou outra muda, como por exemplo o formato do bower.json, mas nada que seja tão assustador e diferente do que você já conhece.

Rails + Bower + Asset Pipeline

As coisas no ambiente de desenvolvimento vão muito bem até que você precisa subir seu projeto pra homologação (veja que nesse caso, na minha humilde opinião, homologação deveria ser uma réplica de produção, mas isso é tema pra outro papo...).

Uma coisa essencial pra que uma aplicação funcione corretamente é testar seus enviroments.

Quando você começa a testar isso na sua máquina, e executa os processos de build pra production, vem a surpresa desagradável. Muitos assets não compilados, erros de paths, chamadas de arquivos dando 404... Começa então a dor de cabeça...

Trabalhando com o Sprockets, precisamos definir algumas configurações específicas dos ambientes, tanto de development, test ou de production.

No Rails, esses arquivos de configuração ficam na pasta config/enviroments. Há um arquivo pra cada tipo de ambiente: development.rb, test.rb e production.rb.

Dentro de cada um existem opções específicas, como por exemplo, quais assets você deseja compilar. Pra isso, basta definir da seguinte forma:

# config/enviroments/production.rb

config.assets.precompile += %w(main.css main.js)

Fazendo assim, em modo production, o Sprockets se encarrega de encontrar seus assets, conforme o nome e extension que você passou.. Nesse caso, ele vai em busca desses main.css|js nos diretórios:

# para CSS
app/assets/stylesheets
vendor/assets/stylesheets
lib/assets/stylesheets 

# para JS
app/assets/javascripts
vendor/assets/javascripts
lib/assets/javascripts

Isso sem contar que, se você usar os arquivos manifestos na raiz da pasta app/assets/stylesheets|javascripts ele se encarregará de compilar por padrão. E além do mais, ele sempre compilará o que estiver nas pastas vendor e lib.

Portanto, é só utilizar a sintaxe de diretivas dele nos manifestos (require e etc.) que tudo vai ser compilado pra você. Veja mais sobre isso, aqui.

Problemas...

Como citei logo no começo, alguns problemas de compilação começam a acontecer. Um dos mais comuns é em relação ao path de arquivos CSS, quando estes contém um valor de propriedade url(). Isso acontece, por exemplo, tanto em carregamento de font-face como de um background-image.

Pra isso, o Sprockets disponibiliza helpers pra que o asset seja encontrado, e a versão compilada seja carregada.

Lembre-se que, no Rails 3.* em diante, a opção de gerar assets com o digest - aquele SHA que é inserido no nome do arquivo pra controle de cache e versão da compilação - vem habilitado por default e não é possível reverter isso sem usar alguma "gambiarra" ou o conhecido monkeypatch, que forçe a compilação non-digest como essa gem non-stupid-digest-assets faz. Chamo de "gambiarra" porque isso muda um comportamento do Rails. Não sei se é boa prática ou não, mas parece que resolve o problema.

Há dois pontos de vista pra esses casos. Um deles é que dessa forma que o Rails trabalha, você tem certeza de que os assets foram compilados pelo Sprockets. O ponto negativo é que, em tese, em todo lugar você precisará inserir o helper asset_path do Sprockets pra que ele sirva a version do asset com o digest no nome.

Daí que mora o grande problema de usar o Bower com o Sprockets. Os componentes geralmente não vem adaptados pra esse tipo de situação. Isso significa que você vai perder a dinamicidade do Bower, pois terá que "forkar" o componente que você precisa e inserir os helpers aonde for necessário. E tem mais... Toda vez que o componente for atualizado você precisará atualizar também seu fork antes de rodar o bower update...

Lembre-se também que nem todo o projeto usa o pré-compilador que você está usando, ou até mesmo não usa nenhum... Sentiu a dorzinha de cabeça?

Solução

Não há bala de prata. E isso tem sido motivo de várias discussões e posts na comunidade. Uns defendem até um by-pass no Asset Pipeline usando outras libs. Outros a adaptação do seu workflow com esse tipo de situação.

O Jean Carlos Emer escreveu um artigo excelente sobre essa discussão defendendo que o uso ou não dele vai depender da sua aplicação, e se for seguir o caminho que o Asset Pipeline trilha, você precisa aceitar esses problemas.

Houve uma discussão sobre isso há alguns meses numa issue do sprockets-rails, falando exatamente dos problemas do non-digest compile. Vale a pena ler os comentários e checar o que o pessoal acha sobre o assunto.

Rails Assets

O Akita postou outro artigo muito bacana sobre o Rails Assets, que também está diretamente relacionado com o gerenciamento de componentes. A proposta do Rails Assets é 'gemificar' todos os componentes que você tem na aplicação. E assim teríamos no seu Gemfile, algo como:

gem rails-assets-bootstrap
gem rails-assets-jquery

A ideia é bacana, mas em alguns testes que fiz com componentes que utilizo, já percebi que não são todos que estão adaptados e funcionando corretamente. O bom é que a maioria, pelo menos os mais famosos, já estão 'gemificados'. Você também pode adicionar seu pacote no próprio site. Bem interessante e promissor.

Conclusão

Como você percebeu, não há aqui uma receita pronta. Cada caso é um caso, por mais clichê que isso soe.

O que enfatizei nesse post é a compatibilidade de um framework, no caso o Rails, com os assets. Isso pode ser muito mais fácil em outras linguagens, ou não.

Independente disso, você poderia desacoplar essa responsabilidade da sua aplicação core, usando outras ferramentas, como o Grunt.

Eu ainda defendo que a melhor solução é aquela em que o desgaste e impacto pra aplicá-la seja o menor. Portanto, seja livre pra decidir com sua equipe qual caminho tomar, variando de acordo com o projeto.

Se tiver algum comentário sobre alguma solução diferente do que falei, contribua aqui embaixo :)


Links desse post

comments powered by Disqus