Para ilustrar a solução vamos direto a um pequeno exemplo. Se for algo pequeno, podemos ficar tentados a colocar tudo no app/assets/application.js, como por exemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
Foo = { hello: function() { return "Hello"; }, world: function() { return "World"; } } Bar = { helloWorld: function() { return Foo.hello() + " " + Foo.world(); } } $(function() { $('body').append('<p>' + Bar.helloWorld() + '</p>'); }) |
Isso vai simplesmente adicionar um parágrafo escrito "Hello World" na página. A idéia é um exemplo bem trivial para que a técnica fique clara, mas obviamente imagine isso em algo mais complicado.
A primeira coisa que gostaríamos de fazer é separar esse código. Talvez quebrar em arquivos como app/assets/javascripts/foo.js e app/assets/javascripts/bar.js. Então vamos direto ao assunto e adicionar o ModuleJS diretamenta à nossa Gemfile:
1 2 3 4 |
# Gemfile source 'https://rails-assets.org' # ... gem 'rails-assets-modulejs' |
Depois de instalar com o bom e velho bundle install. Modificamos o app/assets/javascripts/application.js para carregar a dependência:
1 2 3 4 |
//= require jquery //= require jquery_ujs //= require modulejs //= require_tree . |
Isso foi o que eu já expliquei no meu post mais antigo sobre Rails Assets. Se a biblioteca que você quer tem um pacote Bower, ele pode ser automaticamente encapsulado numa gem de Rails Engine como uma gem feita manualmente. E se não tiver como pacote Bower aí não tem jeito: baixe os arquivos e jogue num diretório em vendor/assets para mantê-la estática dentro do projeto, mas o ideal é que você não deixe bibliotecas javascript vendorizados.
Para começar a refatoração do javascript acima, vamos começar quebrando os objetos 'Foo' e 'Bar' em dois arquivos:
1 2 3 4 5 6 7 8 9 10 11 12 |
// app/assets/javascripts/foo.js modulejs.define( 'foo', function() { return { hello: function() { return "Hello"; }, world: function() { return "World"; } } }) |
1 2 3 4 5 6 7 8 |
// app/assets/javascripts/bar.js modulejs.define( 'bar', [ 'foo' ], function(Foo) { return { helloWorld: function() { return Foo.hello() + " " + Foo.world(); } } }) |
Veja que não precisamos mais das constantes 'Foo' e 'Bar', apenas devolver o corpo do hash com as funções. No módulo 'bar', declaramos que precisamos do módulo 'foo' (em um array) e passamos como parâmetro do construtor com o nome antigo da constante 'Foo', daí internamente o código se mantém exatamente o mesmo. E já que estamos modularizando, vamos adicionar um módulo que encapsula a chamada principal:
1 2 3 4 5 6 7 8 |
// app/assets/javascripts/main.js modulejs.define( 'main', ['bar', 'jquery'], function(Bar, $) { return { start: function() { $('body').append('<p>' + Bar.helloWorld() + '</p>'); } } }) |
Como pode ver acima, o módulo 'main' vai depender do 'bar' (definido em 'bar.js') e também de um 'jquery' que ainda não existe, então vamos criar um genérico modules.js para encapsular o jQuery assim:
1 2 3 4 |
// app/assets/javascripts/modules.js modulejs.define( 'jquery', function() { return jQuery; }); |
Veja que podemos fazer a mesma coisa para qualquer outra biblioteca. Damos um nome em string, no caso 'jquery', que o ModuleJS vai conseguir encontrar depois e retornamos a classe principal, só isso.
Para finalizar no arquivo original application.js chamamos esse novo 'main' assim:
1 2 3 4 |
$(function() { var app = modulejs.require('main'); app.start(); }) |
Pronto! Tudo isolado e com as dependências todas controladas. A grande vantagem de fazer desta forma é que no cabeçalho do application.js podemos continuar declarando as dependências com o require_tree . pois não importa a ordem dos arquivos, apenas a chamada principal do módulo 'main'.
Pronto, não precisamos de RequireJS, podemos continuar usando o Asset Pipeline que vai gerar os arquivos minificados como o Rails sempre fez e com o mínimo de dependências que podem causar conflitos difíceis de rastrear depois. Vá adicionando seus pacotes preferidos de Bower diretamente na Gemfile e continue usando apenas um sistema de gerenciamento de dependência que sabemos que funciona: Bundler.