Thursday, November 10, 2011

ClojureScript on Rails Asset Pipeline

This post is the second part subsequent to Tilt Template for ClojureScript. Now, Tilt template for ClojureScript has worked, so the template should work with Rails asset pipeline. Though it is brief, Ruby on Rails Guides: Asset Pipeline mentions "Registering gems on Tilt enabling Sprockets to find them." So, I tried that..


To make it work, I added just one simple class, which is clementine_rails.rb below:

module Clementine
class ClementineRails < Rails::Engine
initializer :register_clojurescript do |app|
app.assets.register_engine '.cljs', ClojureScriptTemplate
app.assets.register_engine '.clj', ClojureScriptTemplate
end
end
end

That's it. Then Sprockets finds ClojureScriptTemplate.


Let's try ClojureScript on asset pipeline.


I need Rails app in any case, so I created it. Undoubtedly, my Ruby is JRuby, and is bundler/rails gems installed.
rails new rouge

Then, I added "clementine" gem (a name of ClojureScript template gem) to Gemfile. Since the gem is under development, I specified the directory of the gem.

gem 'clementine', :path => "/Users/yoko/Projects/clementine"

Then, typed bundle install so that clementine gem is recognized. I need at least one controller, so, next, I created "Greetings" controller.

rails g controller Greetings index



Well, finally, Clojure stuff comes in. I put the file below in the directory, app/assets/javascripts. The filename is hello.js.clj . This is because Rails asset pipeline strips extensions off. The file, hello.js.clj, will be used as hello.js. Perhaps, the name, "hello" , works, but it doesn't look like javascript.

(ns hello)
(defn ^:export greet [name]
(str "Hellooo " name))

Finishing touch is to add a javascript snippet to use the function defined by ClojureScript. I added three lines in app/views/greetings/index.html.erb:

<script>
alert(hello.greet("ClojureScript"));
</script>
<h1>Greetings#index</h1>
<p>Find me in app/views/greetings/index.html.erb</p>



When I started Rails and requested http://localhost:3000/greetings/index, I saw the alert box successfully!


Tentatively, I set ClojureScript's option, {:optimizations :advanced}, as default. When I looked the contents of hello.js, I confirmed it was the one ClojureScript compiled.


Although the clementine gem should be improved in various ways, ClojureScript is on Rails asset pipeline. If you are interested, watch https://github.com/yokolet/clementine.

Wednesday, November 09, 2011

Tilt Template for ClojureScript

As a JVM language lover, I've written code mixed with more than one language. Ruby gems from Clojure, Clojure from Ruby, or other combinations. This blog is about using ClojureScript from JRuby. ClojureScript (https://github.com/clojure/clojurescript/) is "a new compiler for Clojure that targets JavaScript." ClojureScript uses Google Closure ( don't be confused! ) for optimization. So, instead of JavaScript, we can use Clojure's succinct syntax to write a JavaScript part.


As you know, Clojure is a JVM language. Clojure from JRuby is not so complicated. Many people know what's like that. My attempt here is using ClojureScript from Tilt (https://github.com/rtomayko/tilt). Tilt is a well known generic wrapper for Ruby template engines. It covers more than 20 template engines such as ERB, Haml, or CoffeeScript. Not only those, Tilt allows us to write other template wrappers. Thus, Tilt template for ClojureScript is possible.


Here's what I did. Whole code is on github, https://github.com/yokolet/clementine.

Firstly, I copied jars and Clojure code for ClojureScript under vendor/assets directory. The directory can be lib or other. An important point is to set Java's classpath to those jars and directories. See, clementine.rb below:

require 'rubygems'
require "clementine/version"
if defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
require "java"

CLOJURESCRIPT_HOME = File.dirname(__FILE__) + "/../vendor/assets"
$: << CLOJURESCRIPT_HOME + "/lib"
require 'clojure'

%w{compiler.jar goog.jar js.jar}.each {|name| $CLASSPATH << CLOJURESCRIPT_HOME + "/lib/" + name}
%w{clj cljs}.each {|path| $CLASSPATH << CLOJURESCRIPT_HOME + "/src/" + path}

require "clementine/clojurescript_engine"
require "clementine/clojurescript_template"
end

Line 6 sets CLOJURESCRIPT_HOME directory relative to clementine.rb. CLOJURESCRIPT_HOME + "/lib" directory has jars, so I set this directory to $LOAD_PATH so that JRuby can load clojure.jar. Line 8, "require 'clojure'" loads clojure.jar. You can also write "require 'clojure.jar'" instead, but extension doesn't matter for JRuby. Then, I added paths to $CLASSPATH. We can use $CLASSPATH global variable after "require 'java'". $CLASSPATH is Ruby Array and equivalent to Java's classpath. Line 10 and 11 set all paths to run ClojureScript.


The code below is a ClojureScriptEngine class, clojurescript_engine.rb:

require 'java'
%w{RT Keyword PersistentHashMap}.each do |name|
java_import "clojure.lang.#{name}"
end

module Clementine
class ClojureScriptEngine
def initialize(file, options)
@file = file
@options = options
end

def compile
cl_opts = PersistentHashMap.create(convert_options())
RT.loadResourceScript("cljs/closure.clj")
builder = RT.var("cljs.closure", "build")
builder.invoke(@file, cl_opts)
end

def convert_options()
opts = {}
@options.each do |k, v|
cl_key = Keyword.intern(k.to_s)
case
when (v.kind_of? Symbol)
cl_value = Keyword.intern(v.to_s)
else
cl_value = v
end
opts[cl_key] = cl_value
end
opts
end
end
end

This class basically performs ClojureScript REPL, (cljsc/build "hello.cljs" {:optimizations :advanced :output-to "hello.js"}). The function, cljsc/build, is defined in cljs/closure.clj file, so the code loads cljs/closure.clj by clojure.lang.RT.oadResourceScript("cljs/closure.clj"). Line 16 gets a reference to the method, then, line 17 invokes the method with arguments.
This gist https://gist.github.com/1350398 might be more understandable since it is straightforward.


Lastly, Tilt templete code, clojurescript_template.rb, became as in below:

require 'tilt/template'

module Clementine
class ClojureScriptTemplate < Tilt::Template
self.default_mime_type = 'application/javascript'

def self.engine_initialized?
true
end

def initialize_engine; end

def prepare
@engine = ClojureScriptEngine.new(@file, options)
end

def evaluate(scope, locals, &block)
@output ||= @engine.compile
end
end
end

Probably, you'd better to go to Tilt sites rather than to read my how-to about this code. So, I won't comment anything about this code.


Let's try ClojureScript Tilt template.

$ cd clementine
$ jruby -Ilib -S irb
jruby-1.6.5 :001 > require 'clementine'
=> true
jruby-1.6.5 :002 > require 'tilt'
=> true
jruby-1.6.5 :003 > Tilt.register(Clementine::ClojureScriptTemplate, 'cljs', 'clj')
=> ["cljs", "clj"]
jruby-1.6.5 :004 > template = Tilt.new('/Users/yoko/Works/tmp/clojurescript/hello.clj', 1, {:optimizations => :advanced})
=> #<Clementine::ClojureScriptTemplate:0x5513dd59 @compiled_method={}, @reader=#<Proc:0x517667bd@/usr/local/rvm/gems/jruby-1.6.5@jror/gems/tilt-1.3.3/lib/tilt/template.rb:67>, @engine=#<Clementine::ClojureScriptEngine:0x3494d313 @file="/Users/yoko/Works/tmp/clojurescript/hello.clj", @options={:optimizations=>:advanced}>, @options={:optimizations=>:advanced}, @line=1, @file="/Users/yoko/Works/tmp/clojurescript/hello.clj", @default_encoding=nil, @data="(ns hello)\n(defn ^:export greet []\n (str \"Hello \" n))">
jruby-1.6.5 :005 > template.render
=> "function b(a){throw a;}var f=true,h=null,j=false;function aa(){return function(a){return a}}function k(a){return function(){return this[a]}}function l(a){return function(){return
(snip)



It worked!


This is a preliminary implementation and far from a release at this moment. But, I could confirm ClojureScript is available from Ruby. ClojureScript will be added to Ruby's template engines. My next goal will be ClojureScript from Ruby web application frameworks. It'll be more practical.