Integrating Scalate and Jade with Play 1.2.3
At the beginning of this year, I decided I wanted to learn Scala. Since I'm a Web Frameworks Aficionado, I figured the best way to do that would be to learn Lift. I entered these two items on my todo list and let them lie for a couple months. After attending TSSJS 2011 and having a conversation with James Strachan, I added a couple more technologies to my learning list. James had great things to say about both CoffeeScript and Jade and I decided to learn those as well.
In May, Devoxx announced their Call For Papers and I started reminiscing about how awesome last year's trip was. I decided I'd try to get accepted again and started brainstorming about talks I'd like to give. I came up with "Comparing Scala Web Frameworks" and "HTML5 with Play Scala, CoffeeScript and Jade". The reason I chose Play over Lift for the latter talk is because I think it fits a lot more with the MVC mindset I have and the easy-to-learn nature of web frameworks I enjoy using. Both topics sounded very interesting to me, and I figured they'd also inspire me to learn the technologies in a brute-force fashion; where I was under a time constraint and would be embarrassed in front of a large audience if I didn't succeed.
In mid-July, I got an email from Stephan inviting me to speak again at the 10th edition of Devoxx. I smile splashed across my face and I quickly realized I had a lot to learn. Since I was still in vacation mode after summer vacation in Montana, I decided to wait until I returned from Cape Cod to start studying. While on my 2nd summer vacation, I received an email from Devoxx stating that they'd like me present "HTML5 with Play/Scala, CoffeeScript and Jade".
To learn all these technologies, I decided on an In Anger approach - where I would study minimally and learn mostly by doing. I ordered CoffeeScript on August 8th and Programming in Scala, 2nd Edition the following week (August 17th). I started reading both books while traveling the following week (I found CoffeeScript and Scala to be very similar, so I don't know if I'd recommend learning them at the same time). That same week, I started integrating Scalate (Jade) into a new Play Scala application.
Scalate advertises on their homepage that it works with Play via the play-scalate module. They neglect to mention that this module hasn't been updated in over a year or what version of Play it works with. I tried to use the scalate-0.7.2 version and quickly ran into issues. I posted a message to the Scalate Google Group explaining my compilation issues and stacktraces. The response? Crickets.
Next, I tried posting to the Play Google Group and got a much better response. Here's what they said:
Looking at the Scalate module code, I don't think it can work as is
with Play Scala 0.9.1. The latest version is more than 1 year old, and
we have made a lot of changes in the API.
...
The integration of
Scalate is pretty difficult if you plan to get the same kind of
experience than the native Play scala template regarding auto-reload
and error reports.
You can try to port the module to 0.9.1, basically all it has to do is
to provide a plugin that detect changes to scaml file, and recompile
them. No special integration with the Play API is needed.
After learning that play-scalate was out-of-date, I contacted the project owner via GitHub and tried to get everything working with Play 1.2.3 and Scalate 1.5.1. I updated the dependencies in the project, fixed compilation issues and tried to build. No dice:
build: [mkdir] Created dir: /Users/mraible/dev/play-scalate/tmp/classes [scalac] Compiling 7 source files to /Users/mraible/dev/play-scalate/tmp/classes [scalac] error: class file needed by Binding is missing. [scalac] reference type Serializable of package scala refers to nonexisting symbol. [scalac] one error found
I posted this error to the Play Group, discovered it was caused by Scalate 1.5.1 requiring Scala 2.9. I downgraded to Scalate 2.4.1 and got another nice cryptic error:
build: [mkdir] Created dir: /Users/mraible/dev/play-scalate/tmp/classes [scalac] Compiling 7 source files to /Users/mraible/dev/play-scalate/tmp/classes [scalac] error: class file needed by ScalaController is missing. [scalac] reference value dispatch of package <root> refers to nonexisting symbol. [scalac] one error found
Apparently, this was caused by another Scala versioning issue and I was offered a much simpler solution for integrating Scalate. Below are the steps I performed to integrate Scalate 1.4.1 with Play 1.2.3.
- Updated dependencies.yml to references Scala and Scalate dependencies.
require: - play - play -> scala 0.9.1 - org.fusesource.scalate -> scalate-core 1.4.1: transitive: false - org.fusesource.scalate -> scalate-util 1.4.1: transitive: false
- Added Scalate configuration elements to application.conf.
scalate=jade jvm.memory=-Xmx256M
- Created a ScalateTemplate class to contain the Scalate Engine and render the template.
package controllers import play.Play object ScalateTemplate { import org.fusesource.scalate._ import org.fusesource.scalate.util._ lazy val scalateEngine = { val engine = new TemplateEngine engine.resourceLoader = new FileResourceLoader(Some(Play.getFile("/app/views"))) engine.classpath = Play.getFile("/tmp/classes").getAbsolutePath engine.workingDirectory = Play.getFile("tmp") engine.combinedClassPath = true engine.classLoader = Play.classloader engine } case class Template(name: String) { val scalateType = "." + Play.configuration.get("scalate"); def render(args: (Symbol, Any)*) = { scalateEngine.layout(name + scalateType, args.map { case (k, v) => k.name -> v } toMap) } } def apply(template: String) = Template(template) }
- Created a Scalate trait to override the render() method in Play's Controller class.
package controllers import play.mvc.Http trait Scalate { def render(args: (Symbol, Any)*) = { def defaultTemplate = Http.Request.current().action.replace(".", "/") ScalateTemplate(defaultTemplate).render(args: _*); } }
- Created an Application.scala controller with a default index method.
package controllers import play.mvc._ import models._ object Application extends Controller with Scalate { def index = { render('user -> User("Raible")) } }
The models/User.scala class is very simple:package models case class User(name:String)
- Lastly, I created an index.jade file in views/Application.
-@ var user: models.User p Hi #{user.name}, - for(i <- 1 to 3) p= i p See, I can count!
After getting all this working, I decided it was time to get it into production. As luck would have it, Heroku had just announced Play support a few days earlier. I heard through the grapevine that Play Scala would work, so I gave it a try. It was amazingly easy. All I had to do was create an account, create a "Procfile" in my application's root directory and run a heroku
command followed by a git push
. It all looked great until Play tried to compile my Jade templates as Groovy templates:
Cannot start in PROD mode with errors Template compilation error (In /app/views/Application/index.jade around line 2) The template /app/views/Application/index.jade does not compile : #{user.name} is not closed. play.exceptions.TemplateCompilationException: #{user.name} is not closed. at play.templates.TemplateCompiler.generate(TemplateCompiler.java:102) at play.templates.TemplateCompiler.compile(TemplateCompiler.java:15) at play.templates.GroovyTemplateCompiler.compile(GroovyTemplateCompiler.java:4 1)
The solution from Guillaume was simple enough and I renamed my "views" directory to "templates" and updated ScalateTemplate.scala accordingly. You can see the deployed application at http://play-more.herokuapp.com.
The next steps in my Play Scala adventure will be trying to get the CoffeeScript module to work. I also hope to integrate HTML5 Boilerplate with Jade and Scalate Layouts. I'll be doing this with the mindset that HTML and JavaScript aren't that bad. I expect a lot from CoffeeScript and Jade and hope I enjoy them as much as Strachan.
In the meantime, here's some interesting links I've seen recently encountered that discuss Scala and/or Play. I dig the passion and activity that exists in these communities.
- Yes, Virginia, Scala is hard and the followup.
- Scalatra vs Unfiltered vs Lift vs Play
- Introducing Play 2.0